├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── wldev │ │ └── expandablecardviewlist │ │ ├── MainActivity.java │ │ ├── MyApplication.java │ │ ├── bindings │ │ └── Bindings.java │ │ ├── data │ │ ├── ExpandStateItem.java │ │ └── Item.java │ │ ├── extra │ │ ├── ClickAdapter.java │ │ ├── MyHTMLTagHandler.java │ │ └── ViewAnimationUtils.java │ │ └── recyclerview │ │ ├── BindingViewHolder.java │ │ └── ExpandableRecyclerViewAdapter.java │ └── res │ ├── anim │ ├── scale_anim.xml │ └── scale_anim_reverse.xml │ ├── animator │ ├── path_morph_down_to_up.xml │ └── path_morph_up_to_down.xml │ ├── drawable-hdpi │ ├── ic_arrow_down.png │ └── ic_arrow_up.png │ ├── drawable-mdpi │ ├── ic_arrow_down.png │ └── ic_arrow_up.png │ ├── drawable-xhdpi │ ├── ic_arrow_down.png │ └── ic_arrow_up.png │ ├── drawable-xxhdpi │ ├── ic_arrow_down.png │ └── ic_arrow_up.png │ ├── layout │ ├── activity_main.xml │ └── rv_item.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenrecord.webm └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the simple project demonstrating how to do expandable CardView list with flexible space between each. 2 | 3 | ![alt tag](https://github.com/Ekalips/ExpandableCardViewList/blob/newMaster/screenrecord.webm) 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion '28.0.3' 6 | defaultConfig { 7 | applicationId "com.wldev.expandablecardviewlist" 8 | minSdkVersion 16 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | dataBinding.enabled = true 22 | 23 | } 24 | 25 | ext.supportVersion = '28.0.0' 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | implementation "com.android.support:appcompat-v7:$supportVersion" 30 | implementation "com.android.support:support-v4:$supportVersion" 31 | implementation "com.android.support:recyclerview-v7:$supportVersion" 32 | implementation "com.android.support:cardview-v7:$supportVersion" 33 | implementation "com.android.support:design:$supportVersion" 34 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 35 | } 36 | -------------------------------------------------------------------------------- /app/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 /home/wldev/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | 8 | import com.wldev.expandablecardviewlist.data.Item; 9 | import com.wldev.expandablecardviewlist.databinding.ActivityMainBinding; 10 | import com.wldev.expandablecardviewlist.recyclerview.ExpandableRecyclerViewAdapter; 11 | 12 | import java.util.ArrayList; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | public static final String TAG = MainActivity.class.getSimpleName(); 17 | ActivityMainBinding binding; 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | binding = DataBindingUtil.setContentView(this,R.layout.activity_main); 22 | binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); 23 | final ArrayList arrayList = new ArrayList<>(); 24 | arrayList.add(new Item("Freelancer", getString(R.string.first_plan))); 25 | arrayList.add(new Item("Startup", getString(R.string.second_plan))); 26 | arrayList.add(new Item("Agency", getString(R.string.third_plan))); 27 | arrayList.add(new Item("Corporate", getString(R.string.full_plan))); 28 | binding.recyclerView.setAdapter(new ExpandableRecyclerViewAdapter(arrayList)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * Created by wldev on 3/10/17. 7 | */ 8 | 9 | public class MyApplication extends Application{ 10 | private static MyApplication applicationInstance; 11 | 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | applicationInstance = this; 16 | } 17 | 18 | public static MyApplication get() { 19 | return applicationInstance; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/bindings/Bindings.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist.bindings; 2 | 3 | import android.databinding.BindingAdapter; 4 | import android.graphics.Color; 5 | import android.graphics.PorterDuff; 6 | import android.graphics.PorterDuffColorFilter; 7 | import android.graphics.drawable.Drawable; 8 | import android.os.Build; 9 | import android.text.Html; 10 | import android.util.Log; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.animation.Animation; 14 | import android.widget.ImageView; 15 | import android.widget.TextView; 16 | 17 | import com.wldev.expandablecardviewlist.extra.MyHTMLTagHandler; 18 | import com.wldev.expandablecardviewlist.extra.ViewAnimationUtils; 19 | 20 | /** 21 | * Created by wldev on 3/10/17. 22 | */ 23 | 24 | public class Bindings { 25 | private static final String TAG = Bindings.class.getSimpleName(); 26 | 27 | @BindingAdapter("android:layout_height") 28 | public static void setLayoutHeight(View view, float height) { 29 | ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 30 | layoutParams.height = (int) height; 31 | view.setLayoutParams(layoutParams); 32 | } 33 | 34 | @BindingAdapter("android:layout_width") 35 | public static void setLayoutWidth(View view, float width) { 36 | ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 37 | layoutParams.width = (int) width; 38 | view.setLayoutParams(layoutParams); 39 | } 40 | 41 | @BindingAdapter({"layout_marginLeft"}) 42 | public static void setMarginLeft(View layout, float margin) { 43 | if (layout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams){ 44 | ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) layout.getLayoutParams(); 45 | params.leftMargin = (int) margin; 46 | layout.setLayoutParams(params); 47 | } 48 | } 49 | 50 | @BindingAdapter({"layout_marginRight"}) 51 | public static void setMarginRight(View layout, float margin) { 52 | if (layout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams){ 53 | ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) layout.getLayoutParams(); 54 | params.rightMargin = (int) margin; 55 | layout.setLayoutParams(params); 56 | } 57 | } 58 | 59 | @BindingAdapter({"layout_marginTop"}) 60 | public static void setMarginTop(View layout, float margin) { 61 | if (layout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams){ 62 | ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) layout.getLayoutParams(); 63 | params.topMargin = (int) margin; 64 | layout.setLayoutParams(params); 65 | } 66 | } 67 | 68 | @BindingAdapter({"layout_marginBottom"}) 69 | public static void setMarginBottom(View layout, float margin) { 70 | if (layout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams){ 71 | ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) layout.getLayoutParams(); 72 | params.bottomMargin = (int) margin; 73 | layout.setLayoutParams(params); 74 | } 75 | } 76 | 77 | @BindingAdapter({"onClick"}) 78 | public static void setOnClick(View view, View.OnClickListener listener){ 79 | view.setOnClickListener(listener); 80 | } 81 | 82 | @BindingAdapter({"backgroundTint"}) 83 | public static void setBackhroundTint(View textView, String item) { 84 | Drawable drawable = textView.getBackground(); 85 | drawable.setColorFilter(new 86 | PorterDuffColorFilter(Color.parseColor(item), PorterDuff.Mode.MULTIPLY)); 87 | textView.setBackground(drawable); 88 | } 89 | 90 | @BindingAdapter({"html"}) 91 | public static void setHTMLTextToTV(TextView textView, String text){ 92 | setHTMLTextToTV(textView, text,false); 93 | } 94 | 95 | @BindingAdapter({"html","useChecks"}) 96 | public static void setHTMLTextToTV(TextView textView, String text, boolean useChecks){ 97 | Log.d(TAG, "setHTMLTextToTV: "); 98 | if (text == null || text.length()==0) { 99 | textView.setText(""); 100 | return; 101 | } 102 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 103 | textView.setText(Html.fromHtml(text,Html.FROM_HTML_MODE_LEGACY,null,new MyHTMLTagHandler(useChecks))); 104 | } 105 | else 106 | textView.setText(Html.fromHtml(text,null,new MyHTMLTagHandler(useChecks))); 107 | } 108 | 109 | 110 | @BindingAdapter({"animate", "animation", "fastAnimation"}) 111 | public static void animateWithRes(ImageView imageView, boolean animate, Animation animation, boolean fastAnimation) { 112 | if (animate && imageView.getAnimation() != null) { 113 | if (fastAnimation) { 114 | animation.setDuration(0); 115 | } 116 | imageView.startAnimation(animation); 117 | } 118 | } 119 | 120 | @BindingAdapter({"reverse", "reverseAnimation", "fastReverse"}) 121 | public static void reverseAnimationWithRes(ImageView imageView, boolean reverse, Animation animation, boolean fastAnimation) { 122 | if (reverse) { 123 | if (fastAnimation) { 124 | animation.setDuration(0); 125 | } 126 | imageView.startAnimation(animation); 127 | } 128 | } 129 | 130 | @BindingAdapter({"expand", "fastExpand"}) 131 | public static void expandView(View view, boolean expand, boolean fast) { 132 | if (expand) { 133 | ViewAnimationUtils.expand(view, null, fast); 134 | } else if (view.getHeight() != 0) { 135 | ViewAnimationUtils.collapse(view, null, fast); 136 | } 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/data/ExpandStateItem.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist.data; 2 | 3 | import android.databinding.BaseObservable; 4 | import android.databinding.Bindable; 5 | 6 | import com.wldev.expandablecardviewlist.BR; 7 | 8 | 9 | /** 10 | * Created by wldev on 7/7/17. 11 | * Purpose: 12 | */ 13 | 14 | public class ExpandStateItem extends BaseObservable { 15 | 16 | @Bindable 17 | private boolean expanded; 18 | 19 | @Bindable 20 | private float margin; 21 | 22 | @Bindable 23 | private boolean fast; 24 | 25 | public ExpandStateItem(boolean expanded) { 26 | this.expanded = expanded; 27 | } 28 | 29 | public ExpandStateItem(boolean expanded, float margin) { 30 | this.expanded = expanded; 31 | this.margin = margin; 32 | } 33 | 34 | public boolean isFast() { 35 | return fast; 36 | } 37 | 38 | public void setFast(boolean fast) { 39 | this.fast = fast; 40 | notifyPropertyChanged(BR.fast); 41 | } 42 | 43 | public boolean isExpanded() { 44 | return expanded; 45 | } 46 | 47 | public float getMargin() { 48 | return margin; 49 | } 50 | 51 | public void setExpanded(boolean expanded) { 52 | this.expanded = expanded; 53 | notifyPropertyChanged(BR.expanded); 54 | } 55 | 56 | public void setMargin(float margin) { 57 | this.margin = margin; 58 | notifyPropertyChanged(BR.margin); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/data/Item.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist.data; 2 | 3 | /** 4 | * Created by wldev on 3/10/17. 5 | */ 6 | 7 | public class Item { 8 | 9 | private String title; 10 | private String desc; 11 | 12 | public Item(String title, String desc) { 13 | this.title = title; 14 | this.desc = desc; 15 | } 16 | 17 | 18 | public String getTitle() { 19 | return title; 20 | } 21 | 22 | public String getDesc() { 23 | return desc; 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/extra/ClickAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist.extra; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by wldev on 3/10/17. 7 | */ 8 | 9 | public abstract class ClickAdapter implements View.OnClickListener{ 10 | @Override 11 | public abstract void onClick(View v); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/extra/MyHTMLTagHandler.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist.extra; 2 | 3 | import android.support.v4.content.ContextCompat; 4 | import android.text.Editable; 5 | import android.text.Html; 6 | import android.text.SpannableStringBuilder; 7 | import android.text.Spanned; 8 | import android.text.style.ForegroundColorSpan; 9 | 10 | import com.wldev.expandablecardviewlist.MyApplication; 11 | import com.wldev.expandablecardviewlist.R; 12 | 13 | import org.xml.sax.XMLReader; 14 | 15 | /** 16 | * Created by wldev on 3/10/17. 17 | */ 18 | 19 | public class MyHTMLTagHandler implements Html.TagHandler { 20 | 21 | private static final String TAG = MyHTMLTagHandler.class.getSimpleName(); 22 | private String listSymbol; 23 | 24 | 25 | public MyHTMLTagHandler(boolean useChecks) { 26 | if (useChecks) 27 | listSymbol = "✓"; 28 | else 29 | listSymbol = "•"; 30 | } 31 | 32 | private boolean first = true; 33 | private String parent = null; 34 | private int index = 1; 35 | 36 | @Override 37 | public void handleTag(boolean opening, String tag, Editable output, 38 | XMLReader xmlReader) { 39 | 40 | SpannableStringBuilder builder = new SpannableStringBuilder(); 41 | ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(ContextCompat.getColor(MyApplication.get(), R.color.colorPrimary)); 42 | 43 | if (tag.equals("ul")) parent = "ul"; 44 | else if (tag.equals("ol")) parent = "ol"; 45 | if (tag.equals("li")) { 46 | if (parent.equals("ul")) { 47 | if (first) { 48 | builder.append("\n\t").append(listSymbol).append(" "); 49 | builder.setSpan(foregroundColorSpan, "\n\t".length(), "\n\t".length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 50 | output.append(builder); 51 | // output.append("\n\t").append(listSymbol).append(" "); 52 | first = false; 53 | } else { 54 | first = true; 55 | } 56 | } else { 57 | if (first) { 58 | builder.append("\n\t").append(String.valueOf(index)).append(". "); 59 | output.append(builder); 60 | first = false; 61 | index++; 62 | } else { 63 | first = true; 64 | } 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/extra/ViewAnimationUtils.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist.extra; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | import android.view.animation.Animation; 6 | import android.view.animation.Transformation; 7 | 8 | /** 9 | * Created by wldev on 3/10/17. 10 | */ 11 | 12 | public class ViewAnimationUtils { 13 | 14 | public static void expand(final View v, final AnimationEndCallback callback, boolean fast) { 15 | v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 16 | final int targtetHeight = v.getMeasuredHeight(); 17 | 18 | v.getLayoutParams().height = 0; 19 | v.setVisibility(View.VISIBLE); 20 | Animation a = new Animation() { 21 | @Override 22 | protected void applyTransformation(float interpolatedTime, Transformation t) { 23 | v.getLayoutParams().height = interpolatedTime == 1 24 | ? ViewGroup.LayoutParams.WRAP_CONTENT 25 | : (int) (targtetHeight * interpolatedTime); 26 | v.requestLayout(); 27 | } 28 | 29 | @Override 30 | public boolean willChangeBounds() { 31 | return true; 32 | } 33 | }; 34 | 35 | int duration = 0; 36 | 37 | if (!fast) { 38 | duration = (int) (targtetHeight / v.getContext().getResources().getDisplayMetrics().density); 39 | } 40 | 41 | a.setDuration(duration); 42 | a.setAnimationListener(new Animation.AnimationListener() { 43 | @Override 44 | public void onAnimationStart(Animation animation) { 45 | 46 | } 47 | 48 | @Override 49 | public void onAnimationEnd(Animation animation) { 50 | if (callback != null) 51 | callback.onAnimationEnded(); 52 | } 53 | 54 | @Override 55 | public void onAnimationRepeat(Animation animation) { 56 | 57 | } 58 | }); 59 | v.startAnimation(a); 60 | } 61 | 62 | public static void collapse(final View v, final AnimationEndCallback callback, boolean fast) { 63 | final int initialHeight = v.getMeasuredHeight(); 64 | 65 | Animation a = new Animation() { 66 | @Override 67 | protected void applyTransformation(float interpolatedTime, Transformation t) { 68 | if (interpolatedTime == 1) { 69 | v.setVisibility(View.GONE); 70 | } else { 71 | v.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime); 72 | v.requestLayout(); 73 | } 74 | } 75 | 76 | @Override 77 | public boolean willChangeBounds() { 78 | return true; 79 | } 80 | }; 81 | 82 | int duration = 0; 83 | if (!fast) { 84 | duration = (int) (initialHeight / v.getContext().getResources().getDisplayMetrics().density); 85 | } 86 | 87 | a.setDuration(duration); 88 | 89 | a.setAnimationListener(new Animation.AnimationListener() { 90 | @Override 91 | public void onAnimationStart(Animation animation) { 92 | 93 | } 94 | 95 | @Override 96 | public void onAnimationEnd(Animation animation) { 97 | if (callback != null) 98 | callback.onAnimationEnded(); 99 | } 100 | 101 | @Override 102 | public void onAnimationRepeat(Animation animation) { 103 | 104 | } 105 | }); 106 | v.startAnimation(a); 107 | } 108 | 109 | interface AnimationEndCallback { 110 | void onAnimationEnded(); 111 | } 112 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/recyclerview/BindingViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist.recyclerview; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.databinding.ViewDataBinding; 5 | import android.support.annotation.LayoutRes; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.ViewGroup; 9 | 10 | /** 11 | * Created by wldev on 3/10/17. 12 | */ 13 | 14 | public class BindingViewHolder extends RecyclerView.ViewHolder { 15 | 16 | private ViewBinding binding; 17 | 18 | public BindingViewHolder(ViewBinding binding) { 19 | super(binding.getRoot()); 20 | this.binding = binding; 21 | } 22 | 23 | public BindingViewHolder(@LayoutRes int layout, ViewGroup parent) { 24 | this(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layout, parent, false)); 25 | } 26 | 27 | public ViewBinding getBinding() { 28 | return binding; 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/wldev/expandablecardviewlist/recyclerview/ExpandableRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wldev.expandablecardviewlist.recyclerview; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.wldev.expandablecardviewlist.R; 9 | import com.wldev.expandablecardviewlist.data.ExpandStateItem; 10 | import com.wldev.expandablecardviewlist.data.Item; 11 | import com.wldev.expandablecardviewlist.databinding.RvItemBinding; 12 | import com.wldev.expandablecardviewlist.extra.ClickAdapter; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by wldev on 3/10/17. 19 | */ 20 | 21 | public class ExpandableRecyclerViewAdapter extends RecyclerView.Adapter> { 22 | private static final String TAG = ExpandableRecyclerViewAdapter.class.getSimpleName(); 23 | 24 | private static final float MAX_MARGIN = 16; 25 | private static final float MIN_MARGIN = 2; 26 | 27 | private ValueAnimator marginAnimator = ValueAnimator.ofFloat(MAX_MARGIN, MIN_MARGIN); // replace with dimens 28 | 29 | private final List data = new ArrayList<>(); 30 | private final List states = new ArrayList<>(); 31 | private boolean isListExpanded; 32 | 33 | public ExpandableRecyclerViewAdapter(List originData) { 34 | marginAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 35 | @Override 36 | public void onAnimationUpdate(ValueAnimator animation) { 37 | for (ExpandStateItem item : 38 | states) { 39 | item.setMargin((float) animation.getAnimatedValue()); 40 | } 41 | } 42 | }); 43 | 44 | setData(originData); 45 | } 46 | 47 | @Override 48 | public BindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 49 | return new BindingViewHolder<>(R.layout.rv_item, parent); 50 | } 51 | 52 | @Override 53 | public void onBindViewHolder(final BindingViewHolder holder, int position) { 54 | holder.getBinding().setItem(data.get(holder.getAdapterPosition())); 55 | holder.getBinding().executePendingBindings(); // We need to set our item here because view will need to know how to measure itself (which data to use, how many text lines and so on) 56 | 57 | 58 | states.get(holder.getAdapterPosition()).setFast(true); 59 | holder.getBinding().setState(states.get(holder.getAdapterPosition())); 60 | 61 | holder.getBinding().setIsLast(holder.getAdapterPosition() == data.size() - 1); 62 | holder.getBinding().setIsFirst(holder.getAdapterPosition() == 0); 63 | 64 | holder.getBinding().setOnClick(new ClickAdapter() { 65 | @Override 66 | public void onClick(View v) { 67 | states.get(holder.getAdapterPosition()).setFast(false); 68 | states.get(holder.getAdapterPosition()).setExpanded(!states.get(holder.getAdapterPosition()).isExpanded()); // just to keep it short 69 | invalidateExpandedState(); 70 | } 71 | }); 72 | 73 | holder.getBinding().executePendingBindings(); 74 | } 75 | 76 | @Override 77 | public int getItemCount() { 78 | return data.size(); 79 | } 80 | 81 | private void invalidateExpandedState() { 82 | boolean isAnyExpanded = false; 83 | for (ExpandStateItem item : 84 | states) { 85 | isAnyExpanded = isAnyExpanded || item.isExpanded(); 86 | } 87 | 88 | if (isListExpanded != isAnyExpanded) { 89 | if (!isAnyExpanded) { 90 | isListExpanded = false; 91 | marginAnimator.start(); 92 | } else { 93 | isListExpanded = true; 94 | marginAnimator.reverse(); 95 | } 96 | } 97 | } 98 | 99 | private void setData(List newData) { 100 | this.data.clear(); 101 | this.states.clear(); 102 | 103 | for (Item i : 104 | newData) { 105 | this.data.add(i); 106 | this.states.add(new ExpandStateItem(false)); // You can actually save expanded states here 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/res/anim/scale_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/scale_anim_reverse.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/animator/path_morph_down_to_up.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/animator/path_morph_up_to_down.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ekalips/ExpandableCardViewList/7cd98a77931a55fa54c7dc711416253283f6f4ef/app/src/main/res/drawable-hdpi/ic_arrow_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ekalips/ExpandableCardViewList/7cd98a77931a55fa54c7dc711416253283f6f4ef/app/src/main/res/drawable-hdpi/ic_arrow_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ekalips/ExpandableCardViewList/7cd98a77931a55fa54c7dc711416253283f6f4ef/app/src/main/res/drawable-mdpi/ic_arrow_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ekalips/ExpandableCardViewList/7cd98a77931a55fa54c7dc711416253283f6f4ef/app/src/main/res/drawable-mdpi/ic_arrow_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ekalips/ExpandableCardViewList/7cd98a77931a55fa54c7dc711416253283f6f4ef/app/src/main/res/drawable-xhdpi/ic_arrow_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ekalips/ExpandableCardViewList/7cd98a77931a55fa54c7dc711416253283f6f4ef/app/src/main/res/drawable-xhdpi/ic_arrow_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ekalips/ExpandableCardViewList/7cd98a77931a55fa54c7dc711416253283f6f4ef/app/src/main/res/drawable-xxhdpi/ic_arrow_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ekalips/ExpandableCardViewList/7cd98a77931a55fa54c7dc711416253283f6f4ef/app/src/main/res/drawable-xxhdpi/ic_arrow_up.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 16 | 17 | 21 | 22 | 23 | 24 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/rv_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 19 | 20 | 23 | 24 | 27 | 28 | 29 | 37 | 38 | 39 | 45 | 46 | 54 | 55 | 56 | 60 | 61 | 68 | 69 | 81 | 82 | 83 | 90 | 91 | 99 | 100 | 105 | 106 | 113 | 114 |