├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── mipmap-xxhdpi │ │ │ ├── mess.jpg │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── drawable │ │ │ ├── ic_notifications.png │ │ │ ├── ic_arrow_downward.xml │ │ │ └── ic_launcher_background.xml │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable-v24 │ │ │ ├── arrow_down.xml │ │ │ ├── arrow_down2.xml │ │ │ └── ic_launcher_foreground.xml │ │ ├── menu │ │ │ └── home_option_menu.xml │ │ └── layout │ │ │ ├── notifications_activity.xml │ │ │ ├── layout_expandable_header.xml │ │ │ ├── notification_row.xml │ │ │ ├── custom_content.xml │ │ │ ├── layout_expandable_content.xml │ │ │ └── activity_main.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── Cairo-Black.ttf │ │ │ ├── Cairo-Bold.ttf │ │ │ ├── Cairo-Light.ttf │ │ │ ├── Cairo-Regular.ttf │ │ │ ├── Cairo-SemiBold.ttf │ │ │ └── Cairo-ExtraLight.ttf │ │ ├── java │ │ └── sample │ │ │ └── com │ │ │ └── expanded_layout │ │ │ ├── notifications │ │ │ ├── NotificationsDM.java │ │ │ ├── NotificationsActivity.java │ │ │ └── NotificationsAdapter.java │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── expandableLayout ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── values │ │ │ ├── values.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── styleable.xml │ │ ├── drawable │ │ │ └── exp_header_arrow.xml │ │ └── layout │ │ │ ├── expandable_layout.xml │ │ │ └── layout_header.xml │ │ └── java │ │ └── widget │ │ └── com │ │ └── expandablelayout │ │ ├── Utils.java │ │ ├── DimenUtils.java │ │ ├── BindingUtil.java │ │ ├── AnimationUtils.java │ │ └── ExpandableLayout.java ├── proguard-rules.pro ├── build.gradle ├── install.gradle ├── bintray.data.gradle └── bintray.gradle ├── settings.gradle ├── images ├── Demo.gif └── Demo2.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── encodings.xml ├── markdown-navigator │ └── profiles_settings.xml ├── vcs.xml ├── misc.xml ├── runConfigurations.xml ├── gradle.xml ├── codeStyles │ └── Project.xml └── markdown-navigator.xml ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── gradlew └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /expandableLayout/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':expandableLayout' 2 | -------------------------------------------------------------------------------- /images/Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/images/Demo.gif -------------------------------------------------------------------------------- /images/Demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/images/Demo2.gif -------------------------------------------------------------------------------- /expandableLayout/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /expandableLayout/src/main/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/mess.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-xxhdpi/mess.jpg -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Cairo-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/assets/fonts/Cairo-Black.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Cairo-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/assets/fonts/Cairo-Bold.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Cairo-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/assets/fonts/Cairo-Light.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Cairo-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/assets/fonts/Cairo-Regular.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Cairo-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/assets/fonts/Cairo-SemiBold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Cairo-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/assets/fonts/Cairo-ExtraLight.ttf -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/drawable/ic_notifications.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mu7amadfawzy/ExpandableLayout/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F3E3E 4 | #222222 5 | #D81B60 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ExpandedLayout 3 | 4 | fonts/Cairo-SemiBold.ttf 5 | fonts/Cairo-SemiBold.ttf 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /expandableLayout/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5dp 4 | 14sp 5 | 14sp 6 | 1dp 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 29 22:22:17 EET 2020 2 | --warning-mode=all 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStoreBase=GRADLE_USER_HOME 6 | #android.databinding.enableV2=true 7 | zipStorePath=wrapper/dists 8 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 9 | -------------------------------------------------------------------------------- /expandableLayout/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 50dp 3 | 10dp 4 | expandableLayout 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/arrow_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/arrow_down2.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/home_option_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_downward.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /expandableLayout/src/main/res/drawable/exp_header_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/sample/com/expanded_layout/notifications/NotificationsDM.java: -------------------------------------------------------------------------------- 1 | package sample.com.expanded_layout.notifications; 2 | 3 | /** 4 | * Created by Muhammad Noamany on 18,February,2019 5 | */ 6 | public class NotificationsDM { 7 | private String title, desc; 8 | 9 | public String getTitle() { 10 | return title; 11 | } 12 | 13 | public void setTitle(String title) { 14 | this.title = title; 15 | } 16 | 17 | public String getDesc() { 18 | return desc; 19 | } 20 | 21 | public void setDesc(String desc) { 22 | this.desc = desc; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /expandableLayout/src/main/java/widget/com/expandablelayout/Utils.java: -------------------------------------------------------------------------------- 1 | package widget.com.expandablelayout; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.DisplayMetrics; 6 | 7 | /** 8 | * Created by Fawzy on 02,March,2019. 9 | * ma7madfawzy@gmail.com 10 | */ 11 | 12 | public class Utils { 13 | public static float convertPixelsToDp(float px, Context context) { 14 | Resources resources = context.getResources(); 15 | DisplayMetrics metrics = resources.getDisplayMetrics(); 16 | float dp = px / ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); 17 | return dp; 18 | } 19 | 20 | public static float convertDpToPixels(Context context, float dp) { 21 | return dp * context.getResources().getDisplayMetrics().density; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /expandableLayout/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | --warning-mode=all 13 | #android.databinding.enableV2=true 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/notifications_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 20 | 21 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /expandableLayout/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply from: 'install.gradle' 3 | apply from: 'bintray.gradle' 4 | 5 | android { 6 | compileSdkVersion 29 7 | defaultConfig { 8 | minSdkVersion 21 9 | targetSdkVersion 29 10 | versionCode 4 11 | versionName "1.0.0" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | 14 | } 15 | dataBinding { 16 | enabled = true 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | 30 | } 31 | dependencies { 32 | implementation 'androidx.appcompat:appcompat:1.1.0' 33 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 34 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /expandableLayout/install.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | apply from: 'bintray.data.gradle' 3 | 4 | group = publishedGroupId 5 | install { 6 | repositories.mavenInstaller { 7 | pom.project { 8 | packaging 'aar' 9 | groupId publishedGroupId 10 | artifactId artifact 11 | 12 | name libraryName 13 | description libraryDescription 14 | url siteUrl 15 | 16 | licenses { 17 | license { 18 | name licenseName 19 | url licenseUrl 20 | } 21 | } 22 | developers { 23 | developer { 24 | id developerId 25 | name developerName 26 | email developerEmail 27 | } 28 | } 29 | scm { 30 | connection gitUrl 31 | developerConnection gitUrl 32 | url siteUrl 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_expandable_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 18 | 19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /expandableLayout/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3A339F 4 | #312B9E 5 | #F57D12 6 | #914703 7 | #253E398B 8 | 9 | #9BB84A 10 | #ed1c24 11 | #4b9171 12 | #504b9171 13 | #804b9171 14 | #056839 15 | #bbebd1 16 | #ffffff 17 | #000 18 | #fcbe12 19 | 20 | 21 | #555555 22 | #FFB3B3B3 23 | #00000000 24 | 25 | #000000 26 | #757575 27 | @color/colorPrimary 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | defaultConfig { 6 | applicationId "com.widget.expandableLayout" 7 | minSdkVersion 21 8 | targetSdkVersion 29 9 | versionCode 1 10 | versionName "1.0.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | dataBinding { 14 | enabled = true 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_8 24 | targetCompatibility JavaVersion.VERSION_1_8 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation 'androidx.appcompat:appcompat:1.1.0' 30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 31 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 32 | implementation project(':expandableLayout') 33 | implementation 'com.intuit.sdp:sdp-android:1.0.6' 34 | implementation 'com.intuit.ssp:ssp-android:1.0.6' 35 | implementation 'com.google.android.material:material:1.1.0' 36 | } 37 | -------------------------------------------------------------------------------- /expandableLayout/bintray.data.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | bintrayRepo = 'expandableLayout' //the repo name in Bintray dashboard 3 | bintrayName = 'com.widget.expandableLayout'//the package name in Bintray repo 4 | libraryName = 'expandableLayout' // the module name in the android studio project 5 | //library name will be like -> publishedGroupId:artifact:libraryVersion 6 | publishedGroupId = 'com.widget' 7 | //the ID you want to add in the 'implementation line' 8 | artifact = 'expandableLayout' 9 | //the artifact you want to add in the 'implementation line' 10 | libraryVersion = '3.4.5' 11 | libraryDescription = 'An Android library that lets you create in a simple and fast Layout in which you can insert your custom layout and just expand and collapse without even writing a single Java/Kotlin line of code.' 12 | 13 | siteUrl = 'https://github.com/ma7madfawzy/ExpandableLayout' 14 | gitUrl = 'https://github.com/ma7madfawzy/ExpandableLayout.git' 15 | 16 | 17 | developerId = 'ma7madfawzy' 18 | developerName = 'Mahmad fawzy' 19 | developerEmail = 'ma7madfawzy@gmail.com' 20 | 21 | licenseName = 'The Apache Software License, Version 1.0' 22 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-1.0.txt' 23 | allLicenses = ["Apache-2.0"] 24 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/notification_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 25 | 26 | -------------------------------------------------------------------------------- /expandableLayout/src/main/java/widget/com/expandablelayout/DimenUtils.java: -------------------------------------------------------------------------------- 1 | package widget.com.expandablelayout; 2 | 3 | import android.graphics.Paint; 4 | import android.graphics.Typeface; 5 | import android.util.TypedValue; 6 | import android.widget.TextView; 7 | 8 | public class DimenUtils { 9 | private static DimenUtils dimenUtils; 10 | 11 | public static DimenUtils getInstance() { 12 | if (dimenUtils == null) 13 | dimenUtils = new DimenUtils(); 14 | return dimenUtils; 15 | } 16 | 17 | public void setTextSize(float textSize, TextView textView) { 18 | textView.setTextSize(TypedValue.COMPLEX_UNIT_PX 19 | , determineTextSize(textView.getTypeface(), textSize)); 20 | } 21 | 22 | private int determineTextSize(Typeface font, float allowableHeight) { 23 | Paint p = new Paint(); 24 | p.setTypeface(font); 25 | 26 | int size = (int) allowableHeight; 27 | p.setTextSize(size); 28 | 29 | float currentHeight = calculateHeight(p.getFontMetrics()); 30 | 31 | while (size != 0 && (currentHeight) > allowableHeight) { 32 | p.setTextSize(size--); 33 | currentHeight = calculateHeight(p.getFontMetrics()); 34 | } 35 | 36 | if (size == 0) { 37 | return (int) allowableHeight; 38 | } 39 | return size; 40 | } 41 | 42 | private float calculateHeight(Paint.FontMetrics fm) { 43 | return fm.bottom - fm.top; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /expandableLayout/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | apply from: 'bintray.data.gradle' 3 | android { 4 | compileSdkVersion 29 5 | } 6 | version = libraryVersion 7 | task sourcesJar(type: Jar) { 8 | archiveClassifier.set('sources') 9 | from android.sourceSets.main.java.srcDirs 10 | } 11 | 12 | task javadoc(type: Javadoc) { 13 | options.addBooleanOption('Xdoclint:none', true) 14 | source = android.sourceSets.main.java.srcDirs 15 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 16 | // classpath += configurations.javadocDeps 17 | classpath += configurations.compile 18 | failOnError false 19 | } 20 | 21 | task javadocJar(type: Jar, dependsOn: javadoc) { 22 | archiveClassifier.set('javadoc') 23 | from javadoc.destinationDir 24 | } 25 | 26 | artifacts { 27 | archives javadocJar 28 | archives sourcesJar 29 | } 30 | 31 | Properties properties = new Properties() 32 | properties.load(new FileInputStream(file(rootProject.file('local.properties')))) 33 | 34 | bintray { 35 | user = properties.getProperty("bintray.user") 36 | key = properties.getProperty("bintray.apikey") 37 | 38 | configurations = ['archives'] 39 | pkg { 40 | repo = bintrayRepo 41 | name = bintrayName 42 | desc = libraryDescription 43 | websiteUrl = siteUrl 44 | vcsUrl = gitUrl 45 | userOrg = userOrg 46 | licenses = allLicenses 47 | publish = true 48 | publicDownloadNumbers = true 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/custom_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 17 | 18 | 23 | 24 | 25 | 26 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /expandableLayout/src/main/java/widget/com/expandablelayout/BindingUtil.java: -------------------------------------------------------------------------------- 1 | package widget.com.expandablelayout; 2 | 3 | import android.graphics.Typeface; 4 | import android.graphics.drawable.Drawable; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import androidx.databinding.BindingAdapter; 10 | 11 | /** 12 | * Created by Fawzy on 02,March,2019. 13 | * ma7madfawzy@gmail.com 14 | */ 15 | 16 | public class BindingUtil { 17 | @BindingAdapter({"exp_src"}) 18 | public static void setImageBackground(ImageView view, Drawable drawable) { 19 | if (drawable == null) 20 | return; 21 | view.setBackground(null); 22 | view.setBackgroundResource(0); 23 | view.setImageDrawable(drawable); 24 | } 25 | 26 | @BindingAdapter("exp_visibility") 27 | public static void visibility(View view, boolean visible) { 28 | view.setVisibility(visible ? View.VISIBLE : View.GONE); 29 | } 30 | 31 | @BindingAdapter("exp_fontPath") 32 | public static void setFont(TextView view, String fontPath) { 33 | if (fontPath != null) { 34 | try { 35 | Typeface type = Typeface.createFromAsset(view.getContext().getAssets(), fontPath); 36 | view.setTypeface(type, view.getTypeface().getStyle()); 37 | } catch (Exception e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | } 42 | 43 | @BindingAdapter({"exp_content"}) 44 | public static void setContent(ExpandableLayout view, String content_text) { 45 | view.setContent(content_text); 46 | } 47 | 48 | @BindingAdapter({"exp_title"}) 49 | public static void setTitle(ExpandableLayout view, String content_text) { 50 | view.setTitle(content_text); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /expandableLayout/src/main/res/values/styleable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/java/sample/com/expanded_layout/notifications/NotificationsActivity.java: -------------------------------------------------------------------------------- 1 | package sample.com.expanded_layout.notifications; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | 8 | import androidx.appcompat.app.AppCompatActivity; 9 | import androidx.databinding.DataBindingUtil; 10 | import androidx.recyclerview.widget.LinearLayoutManager; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | import java.util.ArrayList; 14 | 15 | import widget.com.expandedcardview.R; 16 | import widget.com.expandedcardview.databinding.NotificationsActivityBinding; 17 | 18 | 19 | /** 20 | * Created by fawzy on 7/23/18. 21 | */ 22 | public class NotificationsActivity extends AppCompatActivity { 23 | 24 | private NotificationsActivityBinding binding; 25 | private NotificationsAdapter adapter; 26 | 27 | public static void start(Context context) { 28 | Intent intent = new Intent(context, NotificationsActivity.class); 29 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 30 | context.startActivity(intent); 31 | } 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | renderView(); 37 | } 38 | 39 | private void renderView() { 40 | binding = DataBindingUtil.setContentView(this, R.layout.notifications_activity); 41 | setActionBar(binding.toolbar); 42 | initiate(); 43 | } 44 | 45 | private void initiate() { 46 | setRecycler(); 47 | } 48 | 49 | private void setRecycler() { 50 | adapter = new NotificationsAdapter(new ArrayList<>()); 51 | binding.recycler.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false)); 52 | binding.recycler.setAdapter(adapter); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /expandableLayout/src/main/res/layout/expandable_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 21 | 22 | 25 | 26 | 27 | 32 | 33 | 37 | 38 | 42 | 43 | 52 | 53 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_expandable_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 22 | 23 | 31 | 32 | 40 | 41 | 49 | 50 | 59 | 60 | 61 | 65 | 66 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /app/src/main/java/sample/com/expanded_layout/notifications/NotificationsAdapter.java: -------------------------------------------------------------------------------- 1 | package sample.com.expanded_layout.notifications; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.databinding.DataBindingUtil; 9 | import androidx.recyclerview.widget.LinearLayoutManager; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import java.util.List; 13 | 14 | import widget.com.expandablelayout.ExpandableLayout; 15 | import widget.com.expandedcardview.R; 16 | import widget.com.expandedcardview.databinding.NotificationRowBinding; 17 | 18 | 19 | /** 20 | * Created by fawzy 21 | */ 22 | public class NotificationsAdapter extends RecyclerView.Adapter { 23 | private List dataList; 24 | private LayoutInflater layoutInflater; 25 | private NotificationRowBinding binding; 26 | private RecyclerView recyclerView; 27 | private int expandedPos = -1; 28 | 29 | public NotificationsAdapter(List dataList) { 30 | this.dataList = dataList; 31 | } 32 | 33 | @Override 34 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 35 | if (layoutInflater == null) 36 | layoutInflater = LayoutInflater.from(parent.getContext()); 37 | binding = DataBindingUtil.inflate(layoutInflater, 38 | R.layout.notification_row, parent, false); 39 | return new ViewHolder(binding); 40 | } 41 | 42 | @Override 43 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 44 | holder.bind(position); 45 | } 46 | 47 | @Override 48 | public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 49 | super.onAttachedToRecyclerView(recyclerView); 50 | this.recyclerView = recyclerView; 51 | ExpandableLayout.onAttachedToRecycler(); 52 | } 53 | 54 | @Override 55 | public long getItemId(int position) { 56 | return position; 57 | } 58 | 59 | @Override 60 | public int getItemCount() { 61 | return 60;//dataList.size(); 62 | } 63 | 64 | public void setDataList(List faqs) { 65 | this.dataList = faqs; 66 | notifyDataSetChanged(); 67 | } 68 | 69 | class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 70 | private NotificationRowBinding binding; 71 | 72 | 73 | ViewHolder(NotificationRowBinding binding) { 74 | super(binding.getRoot()); 75 | this.binding = binding; 76 | 77 | } 78 | 79 | public void bind(int position) { 80 | binding.expandable.setRecyclerItem((LinearLayoutManager) recyclerView.getLayoutManager(), getAdapterPosition()); 81 | } 82 | 83 | @Override 84 | public void onClick(View v) { 85 | //onTitleClicked 86 | } 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /expandableLayout/src/main/res/layout/layout_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 28 | 29 | 30 | 31 | 37 | 38 | 39 | 47 | 48 | 64 | 65 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/java/sample/com/expanded_layout/MainActivity.java: -------------------------------------------------------------------------------- 1 | package sample.com.expanded_layout; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.LinearLayout; 7 | import android.widget.Toast; 8 | 9 | import androidx.appcompat.app.AppCompatActivity; 10 | import androidx.core.content.ContextCompat; 11 | import sample.com.expanded_layout.notifications.NotificationsActivity; 12 | import widget.com.expandablelayout.ExpandableLayout; 13 | import widget.com.expandedcardview.R; 14 | import widget.com.expandedcardview.databinding.LayoutExpandableHeaderBinding; 15 | 16 | public class MainActivity extends AppCompatActivity { 17 | ExpandableLayout expandableLayout; 18 | LinearLayout container; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | expandableLayout = findViewById(R.id.customHeaderExpandable); 25 | container = findViewById(R.id.container); 26 | setAsArrow(); 27 | // setListener(); 28 | // addExpandable(); 29 | // addCustomExpandable(); 30 | } 31 | 32 | private void setAsArrow() { 33 | LayoutExpandableHeaderBinding headerBinding = (LayoutExpandableHeaderBinding) expandableLayout.getHeaderLayoutBinding(); 34 | expandableLayout.setAsArrow(headerBinding.asArrow); 35 | } 36 | 37 | private void addExpandable() { 38 | ExpandableLayout expandableLayout = new ExpandableLayout(this) 39 | .setHeaderTitle("Added By Java", Color.BLACK) 40 | .setDefaultContent("Content xxx xxxxxxxxx xxxxxxxx xxxxxx xxx", Color.BLUE) 41 | .setArrowDrawable(ContextCompat.getDrawable(this, R.drawable.exp_header_arrow)); 42 | container.addView(expandableLayout); 43 | } 44 | 45 | private void addCustomExpandable() { 46 | ExpandableLayout expandableLayout = new ExpandableLayout(this) 47 | .setHeaderTitle(R.layout.layout_expandable_header) 48 | .setContentLayout(R.layout.layout_expandable_content) 49 | .setArrowDrawable(getDrawable(R.drawable.ic_arrow_downward)); 50 | LayoutExpandableHeaderBinding binding = (LayoutExpandableHeaderBinding) expandableLayout.getHeaderLayoutBinding(); 51 | binding.contentDetail.setText("Just changed programmatically"); 52 | container.addView(expandableLayout); 53 | } 54 | 55 | private void setListener() { 56 | expandableLayout.setOnExpandedListener(new ExpandableLayout.OnExpandedListener() { 57 | @Override 58 | public void beforeExpand() { 59 | 60 | } 61 | 62 | @Override 63 | public void onExpandChanged(View v, boolean isExpanded) { 64 | Toast.makeText(MainActivity.this, "isExpanded== " + isExpanded, Toast.LENGTH_SHORT).show(); 65 | } 66 | }); 67 | } 68 | 69 | public void onNotificationsClicked(View view) { 70 | NotificationsActivity.start(this); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /expandableLayout/src/main/java/widget/com/expandablelayout/AnimationUtils.java: -------------------------------------------------------------------------------- 1 | package widget.com.expandablelayout; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.util.Property; 7 | import android.view.View; 8 | import android.view.animation.AccelerateInterpolator; 9 | import android.view.animation.Animation; 10 | import android.view.animation.DecelerateInterpolator; 11 | import android.view.animation.RotateAnimation; 12 | import android.widget.TextView; 13 | 14 | /** 15 | * Created by Fawzy on 02,March,2019. 16 | * ma7madfawzy@gmail.com 17 | */ 18 | class AnimationUtils { 19 | final static int COLLAPSING = 0; 20 | final static int EXPANDING = 1; 21 | private static AnimationUtils animationUtils; 22 | 23 | static AnimationUtils getInstance() { 24 | if (animationUtils == null) 25 | animationUtils = new AnimationUtils(); 26 | return animationUtils; 27 | } 28 | 29 | void rotateAnimation(View view, final boolean expanding, long animDuration) { 30 | float toDegrees = expanding ? 180 : 0; 31 | if (animDuration == 0) { 32 | view.setRotation(toDegrees); 33 | return; 34 | } 35 | float fromDegrees = expanding ? 0 : 180; 36 | RotateAnimation arrowAnimation = new RotateAnimation(fromDegrees, toDegrees 37 | , Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 38 | arrowAnimation.setFillAfter(true); 39 | arrowAnimation.setDuration(animDuration); 40 | view.startAnimation(arrowAnimation); 41 | } 42 | 43 | void animateTextViewMaxLinesChange(final TextView textView, int initialHeight, final int maxLines 44 | , final int duration, boolean expanding, ExpandableLayout.OnExpandedListener listener) { 45 | if (duration == 0) { 46 | textView.setMaxLines(maxLines); 47 | textView.setVisibility(maxLines == 0 ? View.GONE : View.VISIBLE); 48 | return; 49 | } 50 | // final int initialHeight = textView.getMeasuredHeight(); 51 | textView.setMaxLines(maxLines); 52 | textView.measure(View.MeasureSpec.makeMeasureSpec(textView.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, 53 | View.MeasureSpec.UNSPECIFIED)); 54 | final int endHeight = textView.getMeasuredHeight(); 55 | ObjectAnimator animation = ObjectAnimator.ofInt(textView, "maxHeight", initialHeight, endHeight); 56 | animation.addListener(new AnimatorListenerAdapter() { 57 | @Override 58 | public void onAnimationStart(Animator animation) { 59 | if (listener != null && expanding) 60 | listener.beforeExpand(); 61 | textView.setVisibility(View.VISIBLE); 62 | } 63 | 64 | @Override 65 | public void onAnimationEnd(Animator animation) { 66 | if (maxLines == 0) textView.setVisibility(View.GONE); 67 | if (textView.getMaxHeight() == endHeight) { 68 | textView.setMaxLines(maxLines); 69 | } 70 | if (listener != null) 71 | listener.onExpandChanged(textView, expanding); 72 | } 73 | }); 74 | animation.setDuration(duration).start(); 75 | } 76 | 77 | void animateViewHeight(View view, int initialHeight, int targetHeight, long duration, boolean expanding, ExpandableLayout.OnExpandedListener listener) { 78 | ObjectAnimator animator = ObjectAnimator.ofInt(view, new Property(Integer.class, "height") { 79 | @Override 80 | public Integer get(View view) { 81 | return view.getHeight(); 82 | } 83 | 84 | @Override 85 | public void set(View view, Integer value) { 86 | view.getLayoutParams().height = value; 87 | view.setLayoutParams(view.getLayoutParams()); 88 | } 89 | }, initialHeight, targetHeight); 90 | animator.setDuration(duration); 91 | animator.setInterpolator(targetHeight < initialHeight ? new DecelerateInterpolator() : new AccelerateInterpolator()); 92 | animator.start(); 93 | animator.addListener(new AnimatorListenerAdapter() { 94 | @Override 95 | public void onAnimationStart(Animator animation) { 96 | if (listener != null && expanding) 97 | listener.beforeExpand(); 98 | } 99 | 100 | @Override 101 | public void onAnimationEnd(Animator animation) { 102 | if (listener != null) 103 | listener.onExpandChanged(view, expanding); 104 | } 105 | }); 106 | animator.start(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExpandableLayout 2 | [ ![Download](https://api.bintray.com/packages/ma7madfawzy/expandableLayout/com.widget.expandableLayout/images/download.svg) ](https://bintray.com/ma7madfawzy/expandableLayout/com.widget.expandableLayout/_latestVersion) 3 | 4 | An Android library that lets you create an expandable layout in a simple and easy way in which you can use the default header and content OR pass your custom layout and just expand and collapse magic is all ready. 5 | 6 | ![sample](images/Demo2.gif)r 7 | 8 | ## Quick Setup 9 | 10 | ### 1- Include library 11 | 12 | #### Using Gradle 13 | ``` 14 | dependencies { 15 | implementation 'com.widget:expandableLayout:+' 16 | } 17 | ``` 18 | #### Using Maven 19 | ``` 20 | 21 | com.widget 22 | expandableLayout 23 | 3+ 24 | pom 25 | 26 | 27 | ``` 28 | ## 2- Usage 29 | 30 | ### 2.1 XML Layout: 31 | 32 | ``` 33 | 37 | app:duration="400" 38 | 39 | app:hideArrow="true" 40 | app:arrow_icon="@drawable/arrow_down" 41 | app:header_padding="16dp" 42 | app:content_padding="10dp" 43 | 44 | app:startExpanded="true" 45 | 46 | app:showContentFirstLine="true" 47 | 48 | app:content_layout="@layout/custom_content" 49 | app:header_layout="@layout/layout_expandable_header" 50 | 51 | app:exp_title="Header default Text sample" 52 | app:exp_content="Content Text Sample" 53 | 54 | app:header_font="fonts/fontName.ttf" 55 | app:content_font="fonts/fontName.ttf"' 56 | app:header_color="@color/colorAccentDark" 57 | 58 | app:arrow_margin="@dimen/_10sdp" 59 | app:content_color="@color/colorAccent" 60 | app:header_padding="10dp" 61 | app:content_padding="14dp" 62 | app:header_text_style="italic" 63 | app:content_style="bold"/> 64 | 65 | ``` 66 | #### You can use the default HeaderTV and ContentTV: 67 | 68 | ##### ````exp_title```` sets the text of the headerTV . 69 | ##### ````header_color```` sets the textColor of the headerTV. 70 | ##### ````arrow_icon```` sets the resource of the arrowBtn (which is visible with using the default headerTV). 71 | 72 | ##### ````content```` sets the text of the contentTV. 73 | ##### ````content_color```` sets the textColor of the contentTV. 74 | 75 | ##### ````duration```` sets the duration of the collabse and expand animation. 76 | 77 | #### Or you can use set a custom header or a custom content: 78 | 79 | ##### ````header_layout```` sets the declared layout resource as the header layout. 80 | ##### ````content_layout```` sets the declared layout resource as the content layout. 81 | 82 | ##### ````showContentFirstLine```` shows first line of the default content in case collapsed. 83 | 84 | #### Use ```toggle()``` to reverse the state, and use isExpanded() to check if it was expanded or not. 85 | 86 | #### Use ```refresh()``` to remain the state(in case expanded while custom content includes RecyclerView whose data were updated then trigging refresh() will help the expandable sets the expand height well). 87 | 88 | #### ```setOnExpandedListener``` that can be used to listen to state change: 89 | ```` 90 | expandableLayout.setOnExpandedListener(new OnExpandedListener() { 91 | @Override 92 | public void onExpandChanged(View view, boolean isExpanded) { 93 | //handle onExpandChanged 94 | } 95 | //override if needed 96 | @Override 97 | public void beforeExpand() { 98 | } 99 | }); 100 | ```` 101 | #### Override resources to apply custom attributes for a whole project: 102 | ```` 103 | #000000 104 | #757575 105 | @color/colorPrimary 106 | 107 | 5dp 108 | 14sp 109 | 14sp 110 | 1dp 111 | 112 | fonts/myFont.ttf 113 | fonts/myFont.ttf 114 | 115 | 116 | ```` 117 | 118 | ### 2.2 Dynamically: 119 | 120 | ##### Default HeaderTV and ContentTV 121 | ```` 122 | ExpandableLayout expandableLayout = new ExpandableLayout(context) 123 | .setHeaderTitle("Added By Java", Color.BLACK) 124 | .setDefaultContent("Content xxx", Color.BLUE) 125 | .setArrowDrawable(R.drawable.arrow_down); 126 | ```` 127 | 128 | ##### Custom HeaderTV OR ContentTV 129 | ```` 130 | expandableLayout.setHeaderLayout(R.layout.custom_header); 131 | .setContentLayout(R.layout.custom_content); 132 | ```` 133 | ##### Custom Header OR Content 134 | ```` 135 | expandableLayout.setHeaderLayout(R.layout.custom_header) 136 | .setContentLayout(R.layout.custom_content) 137 | ```` 138 | ##### Adding the layout to container view 139 | ```` 140 | container.addView(expandableLayout) 141 | ```` 142 | 143 | ##### Then you can trigger your custom layouts using: 144 | ###### DataBinding 145 | ```` 146 | expandable.getHeaderLayoutBinding();//returns ViewDataBinding which can be cast to your layout binding Impl class 147 | expandable.getContentLayoutBinding(); 148 | ```` 149 | ###### Or just a simple view 150 | ```` 151 | expandable.getHeaderLayoutView(); 152 | expandable.getContentLayoutView(); 153 | ```` 154 | ##### to animate a custom view with expanding and collapsing instead of the default arrow 155 | ```` 156 | expandableLayout.setAsArrow(mArrowImageView); 157 | ```` 158 | ### 2.3 In RecyclerView: 159 | #### In order to setup using in RecyclerView call ```ExpandableLayout.onAttachedToRecycler()``` before binding items of the Adapter. 160 | ##### In order to enable one expanded per time in recyclerViewAdapter.onBind() call ``` setRecyclerItem(linearLayoutManager,itemPosition)``` with the RecyclerView's layoutManger(weather ```GridLayoutManager``` or ```LinearLayoutManager```) 161 | ```` 162 | expandable.setRecyclerItem(linearLayoutManager, getAdapterPosition()); 163 | ```` 164 | 165 | ### Happy Coding 166 | 167 | ## Authors 168 | 169 | * [Mohammed Fawzy](https://github.com/ma7madfawzy) 170 | * [Ali Gamal](https://github.com/aligamal-dev) 171 | * [Muhammad Noamany](https://github.com/muhammadnomany25) 172 | 173 | 174 | ## License 175 | 176 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 177 | 178 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 20 | 21 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 46 | 47 | 55 | 56 | 69 | 70 | 76 | 77 | 86 | 87 | 93 | 94 | 113 | 114 | 120 | 121 | 131 | 132 | 138 | 139 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /expandableLayout/src/main/java/widget/com/expandablelayout/ExpandableLayout.java: -------------------------------------------------------------------------------- 1 | package widget.com.expandablelayout; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.graphics.Typeface; 7 | import android.graphics.drawable.Drawable; 8 | import android.util.AttributeSet; 9 | import android.util.TypedValue; 10 | import android.view.LayoutInflater; 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.LinearLayout; 16 | import android.widget.RelativeLayout; 17 | import android.widget.TextView; 18 | 19 | import androidx.annotation.DrawableRes; 20 | import androidx.core.content.ContextCompat; 21 | import androidx.databinding.DataBindingUtil; 22 | import androidx.databinding.ViewDataBinding; 23 | import androidx.recyclerview.widget.LinearLayoutManager; 24 | import widget.com.expandablecardview.R; 25 | import widget.com.expandablecardview.databinding.ExpandableLayoutBinding; 26 | 27 | import static widget.com.expandablelayout.AnimationUtils.COLLAPSING; 28 | import static widget.com.expandablelayout.AnimationUtils.EXPANDING; 29 | 30 | /** 31 | * Created by Fawzy on 02,March,2019. 32 | * ma7madfawzy@gmail.com 33 | */ 34 | public class ExpandableLayout extends LinearLayout { 35 | private static int expandedPos = -1; 36 | private Animation animation; 37 | private ExpandableLayout.OnExpandedListener listener; 38 | private int duration = 300, itemPosition, headerLayoutRes = -1, contentLayoutRes = -1, headerTextStyle = Typeface.NORMAL, contentTextStyle = Typeface.NORMAL; 39 | private Drawable arrowIconDrawable; 40 | private TypedArray attributesArray; 41 | private boolean isExpanded, startExpanded, hideArrow, showContentFirstLine; 42 | private Context context; 43 | private float header_text_size, content_size, arrow_width, arrow_height, headerPadding, contentPadding, arrowMargin = -1; 44 | private ExpandableLayoutBinding binding; 45 | private String headerFontPath, contentFontPath; 46 | private ViewDataBinding customHeaderBinding, customContentBinding; 47 | private LinearLayoutManager linearLayoutManager; 48 | private View assArrowView; 49 | 50 | public ExpandableLayout(Context context) { 51 | super(context); 52 | init(context); 53 | } 54 | 55 | public ExpandableLayout(Context context, AttributeSet attrs) { 56 | super(context, attrs); 57 | init(context, attrs); 58 | } 59 | 60 | public static void onAttachedToRecycler() { 61 | expandedPos = -1; 62 | } 63 | 64 | private void init(Context context, AttributeSet attrs) { 65 | initAttributes(context, attrs); 66 | init(context); 67 | } 68 | 69 | private void init(Context context) { 70 | this.context = context; 71 | initViews(context); 72 | checkStart(); 73 | } 74 | 75 | private void initAttributes(Context context, AttributeSet attrs) { 76 | attributesArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandableLayout); 77 | headerLayoutRes = attributesArray.getResourceId(R.styleable.ExpandableLayout_header_layout, -1); 78 | contentLayoutRes = attributesArray.getResourceId(R.styleable.ExpandableLayout_content_layout, -1); 79 | headerFontPath = attributesArray.getString(R.styleable.ExpandableLayout_header_font); 80 | contentFontPath = attributesArray.getString(R.styleable.ExpandableLayout_content_font); 81 | duration = attributesArray.getInt(R.styleable.ExpandableLayout_duration, getContext().getResources().getInteger(android.R.integer.config_shortAnimTime)); 82 | arrowIconDrawable = attributesArray.getDrawable(R.styleable.ExpandableLayout_arrow_icon); 83 | startExpanded = attributesArray.getBoolean(R.styleable.ExpandableLayout_startExpanded, false); 84 | header_text_size = attributesArray.getDimension(R.styleable.ExpandableLayout_header_text_size, -1); 85 | arrow_width = attributesArray.getDimension(R.styleable.ExpandableLayout_arrow_width, -1); 86 | arrow_height = attributesArray.getDimension(R.styleable.ExpandableLayout_arrow_height, -1); 87 | hideArrow = attributesArray.getBoolean(R.styleable.ExpandableLayout_hideArrow, false); 88 | content_size = attributesArray.getDimension(R.styleable.ExpandableLayout_content_size, -1); 89 | headerTextStyle = getTypeFace(attributesArray.getInt(R.styleable.ExpandableLayout_header_text_style, Typeface.NORMAL)); 90 | contentTextStyle = getTypeFace(attributesArray.getInt(R.styleable.ExpandableLayout_content_style, Typeface.NORMAL)); 91 | headerPadding = Math.round(attributesArray.getDimension(R.styleable.ExpandableLayout_header_padding, -1)); 92 | contentPadding = Math.round(attributesArray.getDimension(R.styleable.ExpandableLayout_content_padding, -1)); 93 | arrowMargin = Math.round(attributesArray.getDimension(R.styleable.ExpandableLayout_arrow_margin, -1)); 94 | showContentFirstLine = attributesArray.getBoolean(R.styleable.ExpandableLayout_showContentFirstLine, false); 95 | if (contentLayoutRes != -1) 96 | showContentFirstLine = false;//showContentFirstLine is only in case default content 97 | } 98 | 99 | private void initViews(Context context) { 100 | binding = inflateView(context, R.layout.expandable_layout, this); 101 | binding.headerLayout.setHideArrow(hideArrow); 102 | setArrowParams(); 103 | binding.headerLayout.setDrawable(arrowIconDrawable); 104 | binding.headerLayout.getRoot().setOnClickListener(this::onHeaderClicked); 105 | inflateInnerViews(context); 106 | } 107 | 108 | private void checkStart() { 109 | if (startExpanded) { 110 | expand(false); 111 | return; 112 | } 113 | if (isDefaultContent() && showContentFirstLine) 114 | showContentFirstLine(); 115 | } 116 | 117 | 118 | private void setArrowParams() { 119 | if (hideArrow) 120 | return; 121 | float width = -1, height = -1; 122 | if (arrow_width != -1) 123 | width = arrow_width; 124 | if (arrow_height != -1) 125 | height = arrow_height; 126 | if (width == -1 && height == -1) 127 | return; 128 | setParams(binding.headerLayout.arrow, width, height); 129 | } 130 | 131 | private void setParams(View view, float width, float height) { 132 | RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams(); 133 | if (width != -1) 134 | params.width = Math.round(width); 135 | if (height != -1) 136 | params.height = Math.round(height); 137 | view.setLayoutParams(params); 138 | } 139 | 140 | private void inflateInnerViews(Context context) { 141 | inflateHeader(context); 142 | inflateContent(context); 143 | clearAttributes(); 144 | } 145 | 146 | private void clearAttributes() { 147 | if (attributesArray != null) 148 | attributesArray.recycle(); 149 | } 150 | 151 | private void inflateContent(Context context) { 152 | if (isDefaultContent()) 153 | setDefaultContent(); 154 | else inflateContent(context, contentLayoutRes); 155 | } 156 | 157 | private void inflateHeader(Context context) { 158 | if (isDefaultHeader()) 159 | setHeaderTitle(); 160 | else inflateHeader(context, headerLayoutRes); 161 | } 162 | 163 | private void setHeaderTitle() { 164 | binding.setDefaultHeader(true); 165 | if (attributesArray == null) 166 | return; 167 | String headerTxt = attributesArray.getString(R.styleable.ExpandableLayout_exp_title); 168 | int headerTextColor = attributesArray.getColor(R.styleable.ExpandableLayout_header_color, Color.BLACK); 169 | setHeaderTitle(headerTxt, headerTextColor, header_text_size, headerTextStyle); 170 | if (headerPadding != -1) 171 | setPadding(binding.headerLayout.getRoot(), headerPadding, headerPadding, headerPadding, headerPadding); 172 | if (headerFontPath != null) 173 | binding.headerLayout.setFontPath(headerFontPath); 174 | setArrowMargin(); 175 | 176 | } 177 | 178 | private void setDefaultContent() { 179 | binding.setDefaultContent(true); 180 | if (attributesArray == null) 181 | return; 182 | String contentTxt = attributesArray.getString(R.styleable.ExpandableLayout_exp_content); 183 | int contentTextColor = attributesArray.getColor(R.styleable.ExpandableLayout_content_color, Color.BLACK); 184 | setDefaultContent(contentTxt, contentTextColor, contentTextStyle, content_size); 185 | if (contentPadding != -1) 186 | setPadding(getContentView(), contentPadding, contentPadding, contentPadding, contentPadding); 187 | if (headerFontPath != null) 188 | binding.setFontPath(contentFontPath); 189 | } 190 | 191 | private void setPadding(View view, float left, float top, float right, float bottom) { 192 | view.setPadding(Math.round(left), Math.round(top), Math.round(right), Math.round(bottom)); 193 | } 194 | 195 | private void setArrowMargin() { 196 | if (arrowMargin == -1) return; 197 | MarginLayoutParams params = (MarginLayoutParams) binding.headerLayout.arrow.getLayoutParams(); 198 | int marginInt = Math.round(arrowMargin); 199 | params.setMargins(marginInt, marginInt, marginInt, marginInt); 200 | } 201 | 202 | private int getTypeFace(int typeface) { 203 | switch (typeface) { 204 | case 0: 205 | return Typeface.NORMAL; 206 | case 1: 207 | return Typeface.BOLD; 208 | case 2: 209 | return Typeface.ITALIC; 210 | } 211 | return typeface; 212 | } 213 | 214 | public void setHeaderTextStyle(int typeface) { 215 | binding.headerLayout.headerTV.setTypeface(Typeface.defaultFromStyle(typeface)); 216 | } 217 | 218 | private void setContentTextStyle(int typeface) { 219 | binding.contentTV.setTypeface(Typeface.defaultFromStyle(typeface)); 220 | } 221 | 222 | private void inflateHeader(Context context, int viewID) { 223 | binding.setDefaultHeader(false); 224 | // binding.headerLayout.headerLayout.removeAllViews(); 225 | customHeaderBinding = inflateView(context, viewID, binding.headerLayout.headerLayout); 226 | } 227 | 228 | private void inflateContent(Context context, int viewID) { 229 | binding.setDefaultContent(false); 230 | // binding.contentLayout.removeAllViews(); 231 | customContentBinding = inflateView(context, viewID, binding.contentLayout); 232 | } 233 | 234 | private void onHeaderClicked(View v) { 235 | toggle(true); 236 | } 237 | 238 | public void refresh() { 239 | refresh(false); 240 | } 241 | 242 | public void refresh(boolean smoothAnimate) { 243 | if (isExpanded)//measuring content height again and expanding to it 244 | handleMotion(getContentView(), getContentMeasuredHeight(), measureContentHeight(), EXPANDING, smoothAnimate); 245 | else collapse(smoothAnimate); 246 | } 247 | 248 | 249 | public void checkItemState(int itemPosition) { 250 | if (itemPosition == expandedPos && !isExpanded) 251 | expand(false); 252 | else if (itemPosition != expandedPos && isExpanded) 253 | collapse(false); 254 | } 255 | 256 | public void toggle(boolean smoothAnimate) { 257 | if (isExpanded) 258 | collapse(smoothAnimate); 259 | else 260 | expand(smoothAnimate); 261 | } 262 | 263 | public void collapse(boolean smoothAnimate) { 264 | int contentHeight = getContentMeasuredHeight(); 265 | handleMotion(getContentView(), contentHeight, contentHeight, COLLAPSING, smoothAnimate); 266 | } 267 | 268 | public void expand(boolean smoothAnimate) { 269 | handleMotion(getContentView(), showContentFirstLine ? measureContentHeight() : 0, measureContentHeight(), EXPANDING, smoothAnimate); 270 | } 271 | 272 | private void handleMotion(final View view, final int initialHeight, final int distance, final int animationType, boolean smooth) { 273 | isExpanded = animationType == EXPANDING; 274 | checkRecyclerCase(); 275 | if (isDefaultContent()) animateDefaultContentLines(animationType, initialHeight, smooth); 276 | else animateCustomContent(view, initialHeight, distance, animationType, smooth); 277 | if (!hideArrow || assArrowView != null) animateArrow(animationType, duration); 278 | } 279 | 280 | private void animateDefaultContentLines(int animationType, int initialHeight, boolean smooth) { 281 | AnimationUtils.getInstance().animateTextViewMaxLinesChange(binding.contentTV, initialHeight 282 | , animationType == EXPANDING ? Integer.MAX_VALUE 283 | : (showContentFirstLine ? 1 : 0), (smooth ? duration : 0) 284 | , animationType == EXPANDING, listener); 285 | } 286 | 287 | private void showContentFirstLine() { 288 | binding.contentTV.setMaxLines(1); 289 | binding.contentTV.setVisibility(VISIBLE); 290 | } 291 | 292 | private void animateCustomContent(View view, int initialHeight, int distance, int animationType, boolean smooth) { 293 | initialHeight = animationType == EXPANDING ? 0 : initialHeight; 294 | int targetHeight = animationType == EXPANDING ? (initialHeight + distance) : 0; 295 | AnimationUtils.getInstance().animateViewHeight(view, initialHeight 296 | , targetHeight, smooth ? duration : 0, animationType == EXPANDING, listener); 297 | } 298 | 299 | private void setViewHeight(View view, float height) { 300 | view.getLayoutParams().height = Math.round(height); 301 | view.requestLayout(); 302 | } 303 | 304 | private int measureContentHeight() { 305 | return getMeasuredHeight(getContentView()); 306 | } 307 | 308 | private int getContentMeasuredHeight() { 309 | return getContentView().getMeasuredHeight(); 310 | } 311 | 312 | private int getMeasuredHeight(View view) { 313 | view.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY), 314 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); 315 | return view.getMeasuredHeight(); 316 | } 317 | 318 | /** 319 | * used for recyclerView of expandable items 320 | * to handle one-expanded per time case by passing by all visible items in layoutManger and collapse it 321 | **/ 322 | private void checkRecyclerCase() { 323 | if (linearLayoutManager == null || !isExpanded) 324 | return; 325 | boolean atLeastOneCollapsed = false; 326 | expandedPos = itemPosition; 327 | for (int i = linearLayoutManager.findFirstVisibleItemPosition(); i < linearLayoutManager.findLastVisibleItemPosition(); i++) { 328 | ExpandableLayout expandableLayout = linearLayoutManager.findViewByPosition(i).findViewById(getId()); 329 | if (expandableLayout != this && expandableLayout.isExpanded()) { 330 | atLeastOneCollapsed = true; 331 | expandableLayout.collapse(false); 332 | } 333 | } 334 | if (!atLeastOneCollapsed) 335 | expandedPos = -1; 336 | } 337 | 338 | private void updateListener(View view, int animationType) { 339 | if (listener != null) { 340 | listener.onExpandChanged(view, animationType == EXPANDING); 341 | } 342 | } 343 | 344 | private void animateArrow(int animationType, Integer duration) { 345 | AnimationUtils.getInstance().rotateAnimation(getArrowViewToAnimate(), animationType == EXPANDING, duration); 346 | } 347 | 348 | private View getArrowViewToAnimate() { 349 | return assArrowView == null ? binding.headerLayout.arrow : assArrowView; 350 | } 351 | 352 | public ExpandableLayout setAsArrow(View view) { 353 | assArrowView = view; 354 | return this; 355 | } 356 | 357 | private View getContentView() { 358 | return isDefaultContent() ? binding.contentTV : binding.contentLayout; 359 | } 360 | 361 | private T inflateView(Context context, int viewID, ViewGroup root) { 362 | return DataBindingUtil.inflate(LayoutInflater.from(context), 363 | viewID, root, true); 364 | } 365 | 366 | private void setDrawableBackground(ImageView imageView, Drawable drawable) { 367 | if (drawable != null && imageView != null) { 368 | 369 | } 370 | } 371 | 372 | public ExpandableLayout setHeaderTitle(int layoutRes) { 373 | headerLayoutRes = layoutRes; 374 | inflateHeader(context, layoutRes); 375 | return this; 376 | } 377 | 378 | public ExpandableLayout setContentLayout(int layoutRes) { 379 | contentLayoutRes = layoutRes; 380 | inflateContent(context, layoutRes); 381 | return this; 382 | } 383 | 384 | public ExpandableLayout setArrowDrawable(@DrawableRes int drawable) { 385 | return setArrowDrawable(ContextCompat.getDrawable(getContext(), drawable)); 386 | } 387 | 388 | public ExpandableLayout setArrowDrawable(Drawable drawable) { 389 | if (drawable != null) 390 | binding.headerLayout.setDrawable(drawable); 391 | return this; 392 | } 393 | 394 | private void setDefaultContent(String title, int textColor, int contentTextStyle, float content_size) { 395 | setDefaultContent(title, textColor); 396 | setContentTextStyle(contentTextStyle); 397 | setContentTextSize(content_size); 398 | } 399 | 400 | public ExpandableLayout setHeaderTitle(String title, int headerTextColor) { 401 | setTitle(title); 402 | setHeaderLayoutTextColor(headerTextColor); 403 | return this; 404 | } 405 | 406 | public ExpandableLayout setTitle(String title) { 407 | binding.headerLayout.setTitle(title); 408 | return this; 409 | } 410 | 411 | private void setHeaderTitle(String title, int headerTextColor, float header_text_size, int headerTextStyle) { 412 | setHeaderTitle(title, headerTextColor); 413 | setHeaderTextStyle(headerTextStyle); 414 | setHeaderTextSize(header_text_size); 415 | } 416 | 417 | public ExpandableLayout setDefaultContent(String title, int textColor) { 418 | setContent(title); 419 | setDefaultContentTextColor(textColor); 420 | return this; 421 | } 422 | 423 | public ExpandableLayout setContent(String title) { 424 | binding.setContentText(title); 425 | binding.contentTV.setText(title); 426 | return this; 427 | } 428 | 429 | public ExpandableLayout setDefaultContentTextColor(int contentTextColor) { 430 | binding.contentTV.setTextColor(contentTextColor); 431 | return this; 432 | } 433 | 434 | public ExpandableLayout setContentTextSize(float textSize) { 435 | if (textSize != -1) 436 | setTextSize(textSize, binding.contentTV); 437 | return this; 438 | } 439 | 440 | public void setTextSize(float textSize, TextView textView) { 441 | textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 442 | } 443 | 444 | /** 445 | * used for recyclerView of expandable items 446 | * to handle one-expanded per time case 447 | **/ 448 | public void setRecyclerItem(LinearLayoutManager linearLayoutManager, int itemPosition) { 449 | this.linearLayoutManager = linearLayoutManager; 450 | this.itemPosition = itemPosition; 451 | checkItemState(itemPosition); 452 | } 453 | 454 | @Override 455 | public void setLayoutAnimationListener(Animation.AnimationListener animationListener) { 456 | animation.setAnimationListener(animationListener); 457 | } 458 | 459 | public void setOnExpandedListener(ExpandableLayout.OnExpandedListener listener) { 460 | this.listener = listener; 461 | } 462 | 463 | public boolean isExpanded() { 464 | return isExpanded; 465 | } 466 | 467 | public void setHeaderTextSize(float textSize) { 468 | if (textSize != -1) 469 | setTextSize(textSize, binding.headerLayout.headerTV); 470 | } 471 | 472 | public void setHeaderLayoutTextColor(int headerTextColor) { 473 | binding.headerLayout.headerTV.setTextColor(headerTextColor); 474 | } 475 | 476 | public View getHeaderLayoutView() { 477 | return binding.headerLayout.headerLayout; 478 | } 479 | 480 | public View getContentLayoutView() { 481 | return binding.contentLayout; 482 | } 483 | 484 | public ViewDataBinding getHeaderLayoutBinding() { 485 | return customHeaderBinding; 486 | } 487 | 488 | public ViewDataBinding getContentLayoutBinding() { 489 | return customContentBinding; 490 | } 491 | 492 | public void onDetachedFromRecyclerView() { 493 | expandedPos = -1; 494 | } 495 | 496 | private boolean isDefaultContent() { 497 | return contentLayoutRes == -1; 498 | } 499 | 500 | private boolean isDefaultHeader() { 501 | return headerLayoutRes == -1; 502 | } 503 | 504 | public abstract static class OnExpandedListener { 505 | public void beforeExpand() { 506 | } 507 | 508 | public abstract void onExpandChanged(View v, boolean isExpanded); 509 | 510 | } 511 | 512 | } 513 | 514 | --------------------------------------------------------------------------------