diff --git a/README.md b/README.md index d2268f1..05ad3c6 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,269 @@ +[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Context--Menu.Android-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/1387) [![Yalantis](https://github.com/Yalantis/Context-Menu.Android/blob/master/badge.png)](https://yalantis.com/?utm_source=github) [![](https://jitpack.io/v/yalantis/context-menu.android.svg)](https://jitpack.io/#yalantis/context-menu.android) + # ContextMenu -#### You can easily add awesome animated context menu to your app. Made in [Yalantis] (http://yalantis.com/) +#### You can easily add awesome animated context menu to your app. -Check this [project on dribbble] (https://dribbble.com/shots/1785274-Menu-Animation-for-Additional-Functions?list=users&offset=17) +Check this [project on dribbble](https://dribbble.com/shots/1785274-Menu-Animation-for-Additional-Functions?list=users&offset=17) -Check this [project on Behance] (https://www.behance.net/gallery/20411445/Mobile-Animations-Interactions) +Check this [project on Behance](https://www.behance.net/gallery/20411445/Mobile-Animations-Interactions) -![ContextMenu](https://d13yacurqjgara.cloudfront.net/users/125056/screenshots/1785274/99miles-profile-light_1-1-4.gif) + ### Usage: -*For a working implementation, Have a look at the Sample Project - sample* +*For a working implementation, have a look at the ```app``` module* + +#### 1. Clone repository and add sources into your project or use Gradle: -#### 1. Clone repository and add sources into your project . +Add it in your root build.gradle at the end of repositories: +``` + allprojects { + repositories { + ... + maven { url 'https://jitpack.io' } + } + } +``` +Add the dependency +``` + dependencies { + implementation 'com.github.Yalantis:Context-Menu.Android:1.1.4' + } +``` #### 2. Create list of `MenuObject`, which consists of icon or icon and description. +You can use any `drawable, resource, bitmap, color` as image: +``` + menuObject.drawable = ... + menuObject.setResourceValue(...) + menuObject.setBitmapValue(...) + menuObject.setColorValue(...) + ``` +You can set image `ScaleType`: +``` + menuObject.scaleType = ScaleType.FIT_XY +``` +You can use any `resource, drawable, color` as background: +``` + menuObject.setBgResourceValue(...) + menuObject.setBgDrawable(...) + menuObject.setBgColorValue(...) +``` +Now You can easily add text appearance style for menu titles: +``` + In your project styles create style for text appearance + (For better visual effect extend it from TextView.DefaultStyle): + + + And set it's id to your MenuObject : + + val bitmapDrawable = BitmapDrawable( + resources, + BitmapFactory.decodeResource(resources, R.drawable.icn_3) + ) + + val menuObject = MenuObject("Add to friends").apply { + drawable = bitmapDrawable + menuTextAppearanceStyle = R.style.TextViewStyle + } +``` +You can use any `color` as text color: +``` + menuObject.textColor = ... ``` -ArrayList menuObjects = new ArrayList<>(); - menuObjects.add(new MenuObject(R.drawable.icn_close)); - menuObjects.add(new MenuObject(R.drawable.icn_1, "Send message")); - menuObjects.add(new MenuObject(R.drawable.icn_2, "Like profile")); - menuObjects.add(new MenuObject(R.drawable.icn_3, "Add to friends")); - menuObjects.add(new MenuObject(R.drawable.icn_4, "Add to favorites")); - menuObjects.add(new MenuObject(R.drawable.icn_5, "Block user")); +You can set any `color` as divider color: ``` + menuObject.dividerColor = ... +``` +Example: +``` + val close = MenuObject().apply { setResourceValue(R.drawable.icn_close) } + + val send = MenuObject("Send message").apply { setResourceValue(R.drawable.icn_1) } + + val addFriend = MenuObject("Add to friends").apply { + drawable = BitmapDrawable( + resources, + BitmapFactory.decodeResource(resources, R.drawable.icn_3) + ) + } -#### 3. Create `newInstance` of `ContextMenuDialogFragment`, which received menu item size and list of `MenuObject`. + val menuObjects = mutableListOf().apply { + add(close) + add(send) + add(addFriend) + } +``` + +#### 3. Create `newInstance` of `ContextMenuDialogFragment`, which received `MenuParams` object. ``` - mMenuDialogFragment = ContextMenuDialogFragment.newInstance((int) getResources().getDimension(R.dimen.tool_bar_height), menuObjects ); + val menuParams = MenuParams( + actionBarSize = resources.getDimension(R.dimen.tool_bar_height).toInt(), + menuObjects = getMenuObjects(), + isClosableOutside = false + // set other settings to meet your needs + ) + + // If you want to change the side you need to add 'gravity' parameter, + // by default it is MenuGravity.END. + + // For example: + + val menuParams = MenuParams( + actionBarSize = resources.getDimension(R.dimen.tool_bar_height).toInt(), + menuObjects = getMenuObjects(), + isClosableOutside = false, + gravity = MenuGravity.START + ) + + val contextMenuDialogFragment = ContextMenuDialogFragment.newInstance(menuParams) ``` #### 4. Set menu with button, which will open `ContextMenuDialogFragment`. ``` -@Override - public boolean onCreateOptionsMenu(final Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu_main, menu); - return true; + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + return true } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.context_menu: - mMenuDialogFragment.show(fragmentManager, "ContextMenuDialogFragment"); - break; + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + item?.let { + when (it.itemId) { + R.id.context_menu -> { + showContextMenuDialogFragment() + } + } } - return super.onOptionsItemSelected(item); + + return super.onOptionsItemSelected(item) } ``` -#### 5. Implement `ContextMenuDialogFragment.ItemClickListener` interface with `onItemClick` method. - -``` -public class MainActivity extends ActionBarActivity implements ContextMenuDialogFragment.ItemClickListener -… - @Override - public void onItemClick(View clickedView, int position) { -//Do something here +#### 5. Add menu item listeners. +``` + contextMenuDialogFragment = menuItemClickListener = { view, position -> + // do something here + } + + contextMenuDialogFragment = menuItemLongClickListener = { view, position -> + // do something here } ``` ## Customization: For better experience menu item size should be equal to `ActionBar` height. -`newInstance` of `ContextMenuDialogFragment` can receive: +`newInstance` of `ContextMenuDialogFragment` receives `MenuParams` object that has the fields: + +`menuObjects` - list of MenuObject objects, - `animationDelay` - delay in millis after fragment opening and before closing, which will make animation smoother on slow devices, +`animationDelay` - delay in millis after fragment opening and before closing, which will make animation smoother on slow devices, -`animationDuration` - duration of every piece of animation in millis. +`animationDuration` - duration of every piece of animation in millis, + +`isClosableOutside` - if menu can be closed on touch to non-button area, + +`isFitSystemWindows` - if true, then the default implementation of fitSystemWindows(Rect) will be executed, + +`isClipToPadding` - true to clip children to the padding of the group, false otherwise. + +The last two parameters may be useful if you use _Translucent_ parameters in your theme: +``` + true +``` +To stay `Context Menu` below Status Bar set `fitSystemWindows` to true and `clipToPadding` to false. ## Compatibility - * Android Honeycomb 3.0+ + * Android KitKat 4.4+ # Changelog +### Version: 1.1.4 + + * added background color animation + +### Version: 1.1.2 + + * added animation on close outside menu + +### Version: 1.1.1 + + * bug with `menuObject.textColor` was fixed + +### Version: 1.1.0 + + * library rewrited on Kotlin + * added `rtl` support + * added `gravity` parameter for `MenuParams` + +### Version: 1.0.8 + + * added transparent menu background support + * dependencies actualized + +### Version: 1.0.7 + + * Text in menu now also clickable + * Support libs and gradle updated + +### Version: 1.0.6 + + * com.android.tools.build:gradle:1.5.0 + * Support libs and sdk updated to 23 + +### Version: 1.0.5 + + * Fixed `setClosableOutside` [issue](https://github.com/Yalantis/Context-Menu.Android/issues/25). + * Fixed `setAnimationDuration` doesn´t work for open event [issue](https://github.com/Yalantis/Context-Menu.Android/issues/22). + * Fixed menu item listener setting mechanism. It can be not activity but any class that implements listeners now. [Issue](https://github.com/Yalantis/Context-Menu.Android/issues/24). Attention! You have to set listeners to the context fragment manually. Check block 5 in the `Usage`. + +### Version: 1.0.4 + + * Old `ContextMenuDialogFragment` `newInstance` methods are deprecated. Use new universal one that received `MenuParams`. + * Added possibility to dismiss menu by clicking on non-button area. See `MenuParams.setClosableOutside(boolean)`. + +### Version: 1.0.3 + + * Added menu text appearence style. (Note: other text style methods are deprecated). + +### Version: 1.0.2 + + * Changed `MenuObject` constructors. Image setting is moved to methods + * Added styling of `MenuObject` image, background, text color, divider color + * Added possibility to interact with translucent Status Bar + +### Version: 1.0.1 + + * Added `OnMenuItemLongClickListener` (usage: the same as `OnMenuItemClickListener`, check sample app) + * Renamed: +``` +com.yalantis.contextmenu.lib.ContextMenuDialogFragment.ItemClickListener -> +com.yalantis.contextmenu.lib.interfaces.OnMenuItemClickListener + +com.yalantis.contextmenu.lib.ContextMenuDialogFragment.ItemClickListener.onItemClick(...) -> +com.yalantis.contextmenu.lib.interfaces.OnMenuItemClickListener.onMenuItemClick(...) +``` + ### Version: 1.0 * Pilot version +#### Let us know! + +We’d be really happy if you sent us links to your projects where you use our component. Just send an email to github@yalantis.com And do let us know if you have any questions or suggestion regarding the animation. + +P.S. We’re going to publish more awesomeness wrapped in code and a tutorial on how to make UI for Android (iOS) better than better. Stay tuned! + ## License - Copyright 2015, Yalantis + Copyright 2019, Yalantis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index a5a3d2e..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 21 - buildToolsVersion "21.1.2" - - defaultConfig { - applicationId "com.yalantis.contextmenu.sample" - minSdkVersion 11 - targetSdkVersion 21 - versionCode 1 - versionName "1.0" - } -} - -dependencies { - compile project(':lib') -} diff --git a/app/src/main/java/com/yalantis/contextmenu/sample/MainActivity.java b/app/src/main/java/com/yalantis/contextmenu/sample/MainActivity.java deleted file mode 100644 index 1a7d448..0000000 --- a/app/src/main/java/com/yalantis/contextmenu/sample/MainActivity.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.yalantis.contextmenu.sample; - -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.ActionBarActivity; -import android.support.v7.widget.Toolbar; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - - -import com.yalantis.contextmenu.R; -import com.yalantis.contextmenu.lib.ContextMenuDialogFragment; -import com.yalantis.contextmenu.lib.MenuObject; - -import java.util.ArrayList; -import java.util.List; - -public class MainActivity extends ActionBarActivity implements ContextMenuDialogFragment.ItemClickListener { - - private FragmentManager fragmentManager; - private DialogFragment mMenuDialogFragment; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - fragmentManager = getSupportFragmentManager(); - initToolbar(); - mMenuDialogFragment = ContextMenuDialogFragment.newInstance((int) getResources().getDimension(R.dimen.tool_bar_height), getMenuObjects()); - addFragment(new MainFragment(), true, R.id.container); - } - - private List getMenuObjects() { - List menuObjects = new ArrayList<>(); - menuObjects.add(new MenuObject(R.drawable.icn_close)); - menuObjects.add(new MenuObject(R.drawable.icn_1, "Send message")); - menuObjects.add(new MenuObject(R.drawable.icn_2, "Like profile")); - menuObjects.add(new MenuObject(R.drawable.icn_3, "Add to friends")); - menuObjects.add(new MenuObject(R.drawable.icn_4, "Add to favorites")); - menuObjects.add(new MenuObject(R.drawable.icn_5, "Block user")); - return menuObjects; - } - - private void initToolbar() { - Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar); - TextView mToolBarTextView = (TextView) findViewById(R.id.text_view_toolbar_title); - setSupportActionBar(mToolbar); - getSupportActionBar().setHomeButtonEnabled(true); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setDisplayShowTitleEnabled(false); - mToolbar.setNavigationIcon(R.drawable.btn_back); - mToolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - onBackPressed(); - } - }); - mToolBarTextView.setText("Samantha"); - } - - protected void addFragment(Fragment fragment, boolean addToBackStack, int containerId) { - invalidateOptionsMenu(); - String backStackName = fragment.getClass().getName(); - boolean fragmentPopped = fragmentManager.popBackStackImmediate(backStackName, 0); - if (!fragmentPopped) { - FragmentTransaction transaction = fragmentManager.beginTransaction(); - transaction.add(containerId, fragment, backStackName) - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); - if (addToBackStack) - transaction.addToBackStack(backStackName); - transaction.commit(); - } - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu_main, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.context_menu: - mMenuDialogFragment.show(fragmentManager, "DropDownMenuFragment"); - break; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onBackPressed() { - if (fragmentManager.getBackStackEntryCount() == 1) { - finish(); - } else { - super.onBackPressed(); - } - } - - @Override - public void onItemClick(View clickedView, int position) { - Toast.makeText(this, "Position: " + position, Toast.LENGTH_SHORT).show(); - } -} diff --git a/app/src/main/java/com/yalantis/contextmenu/sample/MainFragment.java b/app/src/main/java/com/yalantis/contextmenu/sample/MainFragment.java deleted file mode 100644 index eecfe4e..0000000 --- a/app/src/main/java/com/yalantis/contextmenu/sample/MainFragment.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.yalantis.contextmenu.sample; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.yalantis.contextmenu.R; - -public class MainFragment extends Fragment { - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.fragment_main, container, false); - return rootView; - } -} diff --git a/app/src/main/res/drawable-hdpi/btn_back.png b/app/src/main/res/drawable-hdpi/btn_back.png deleted file mode 100644 index e4fab71..0000000 Binary files a/app/src/main/res/drawable-hdpi/btn_back.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index 96a442e..0000000 Binary files a/app/src/main/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/btn_back.png b/app/src/main/res/drawable-mdpi/btn_back.png deleted file mode 100644 index 8a19ea1..0000000 Binary files a/app/src/main/res/drawable-mdpi/btn_back.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index 359047d..0000000 Binary files a/app/src/main/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/btn_back.png b/app/src/main/res/drawable-xhdpi/btn_back.png deleted file mode 100644 index aa010b8..0000000 Binary files a/app/src/main/res/drawable-xhdpi/btn_back.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 71c6d76..0000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/btn_back.png b/app/src/main/res/drawable-xxhdpi/btn_back.png deleted file mode 100644 index 193484d..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/btn_back.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png deleted file mode 100644 index 4df1894..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/btn_back.png b/app/src/main/res/drawable-xxxhdpi/btn_back.png deleted file mode 100644 index 2841502..0000000 Binary files a/app/src/main/res/drawable-xxxhdpi/btn_back.png and /dev/null differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 3f315c8..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml deleted file mode 100644 index da38ed1..0000000 --- a/app/src/main/res/layout/fragment_main.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/badge.png b/badge.png new file mode 100644 index 0000000..4c764a7 Binary files /dev/null and b/badge.png differ diff --git a/build.gradle b/build.gradle index 6356aab..f62da34 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,25 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { + ext.kotlin_version = '1.3.20' + repositories { + google() jcenter() } - dependencies { - classpath 'com.android.tools.build:gradle:1.0.0' - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + dependencies { + classpath 'com.android.tools.build:gradle:3.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { + google() jcenter() + maven { url 'https://jitpack.io' } } } + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 1d3591c..f04974c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,19 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true + +VERSION_NAME=1.1.0 +VERSION_CODE=10 +GROUP=com.yalantis + +POM_DESCRIPTION=Android Library to display awesome context menu +POM_URL=https://github.com/Yalantis/Context-Menu.Android +POM_SCM_URL=https://github.com/Yalantis/Context-Menu.Android +POM_SCM_CONNECTION=scm:git@github.com/Yalantis/Context-Menu.Android.git +POM_SCM_DEV_CONNECTION=scm:git@github.com/Yalantis/Context-Menu.Android.git +POM_LICENCE_NAME=The Apache Software License, Version 2.0 +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0 +POM_LICENCE_DIST=repo +POM_DEVELOPER_ID=yalantis +POM_DEVELOPER_NAME=Yalantis \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c71e76..d7958b4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Tue Jan 15 12:30:17 EET 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index 3fa3e47..96b49f1 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,19 +1,27 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 21 - buildToolsVersion "21.1.2" + compileSdkVersion 28 defaultConfig { - minSdkVersion 11 - targetSdkVersion 21 - versionCode 1 - versionName "1.0" + minSdkVersion 19 + targetSdkVersion 28 + versionCode Integer.parseInt(project.VERSION_CODE) + versionName VERSION_NAME + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 } } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:21.0.3' - compile 'com.nineoldandroids:library:2.4.0' + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation 'com.android.support:appcompat-v7:28.0.0' } \ No newline at end of file diff --git a/lib/gradle.properties b/lib/gradle.properties new file mode 100644 index 0000000..10bfaff --- /dev/null +++ b/lib/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=Context Menu +POM_ARTIFACT_ID=contextmenu +POM_PACKAGING=aar \ No newline at end of file diff --git a/lib/src/main/AndroidManifest.xml b/lib/src/main/AndroidManifest.xml index 749ab31..4b44572 100644 --- a/lib/src/main/AndroidManifest.xml +++ b/lib/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ - - + - + \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/AnimatorUtils.java b/lib/src/main/java/com/yalantis/contextmenu/lib/AnimatorUtils.java deleted file mode 100644 index d1cc812..0000000 --- a/lib/src/main/java/com/yalantis/contextmenu/lib/AnimatorUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.yalantis.contextmenu.lib; - -import android.view.View; - -import com.nineoldandroids.animation.AnimatorSet; -import com.nineoldandroids.animation.ObjectAnimator; - -public class AnimatorUtils { - - public static ObjectAnimator rotationCloseToRight(View v) { - return ObjectAnimator.ofFloat(v, "rotationY", 0, -90); - } - - public static ObjectAnimator rotationOpenFromRight(View v) { - return ObjectAnimator.ofFloat(v, "rotationY", -90, 0); - } - - public static ObjectAnimator rotationCloseVertical(View v) { - return ObjectAnimator.ofFloat(v, "rotationX", 0, -90); - } - - public static ObjectAnimator rotationOpenVertical(View v) { - return ObjectAnimator.ofFloat(v, "rotationX", -90, 0); - } - - public static ObjectAnimator alfaDisappear(View v) { - return ObjectAnimator.ofFloat(v, "alpha", 1, 0); - } - - public static ObjectAnimator alfaAppear(View v) { - return ObjectAnimator.ofFloat(v, "alpha", 0, 1); - } - - public static ObjectAnimator translationRight(View v, float x) { - return ObjectAnimator.ofFloat(v, "translationX", 0, x); - } - public static ObjectAnimator translationLeft(View v, float x) { - return ObjectAnimator.ofFloat(v, "translationX", x, 0); - } - - public static AnimatorSet fadeOutSet(View v, float x){ - AnimatorSet fadeOutSet = new AnimatorSet(); - fadeOutSet.playTogether(alfaDisappear(v), translationRight(v,x)); - return fadeOutSet; - } - -} diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/ContextMenuDialogFragment.java b/lib/src/main/java/com/yalantis/contextmenu/lib/ContextMenuDialogFragment.java deleted file mode 100644 index 82ddd15..0000000 --- a/lib/src/main/java/com/yalantis/contextmenu/lib/ContextMenuDialogFragment.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.yalantis.contextmenu.lib; - -import android.app.Activity; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.DialogFragment; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.LinearLayout; -import java.util.ArrayList; -import java.util.List; - -public class ContextMenuDialogFragment extends DialogFragment implements MenuAdapter.OnItemClickListener { - - private static final String ACTION_BAR_SIZE = "action_bar_size"; - private static final String MENU_OBJECTS = "menu_objects"; - private static final String ANIMATION_DELAY = "animation_delay"; - private static final String ANIMATION_DURATION = "animation_duration"; - - private LinearLayout mWrapperButtons; - private LinearLayout mWrapperText; - private MenuAdapter mDropDownMenuAdapter; - private ArrayList mMenuObjects; - private int mActionBarHeight; - private ItemClickListener mItemClickListener; - private int mAnimationDelay = 0; // delay after opening and before closing dialogfragent - private int mAnimationDuration; - - public interface ItemClickListener { - public void onItemClick(View clickedView, int position); - } - - public static ContextMenuDialogFragment newInstance(int actionBarSize, List menuObjects) { - ContextMenuDialogFragment contextMenuDialogFragment = new ContextMenuDialogFragment(); - Bundle args = new Bundle(); - args.putInt(ACTION_BAR_SIZE, actionBarSize); - args.putParcelableArrayList(MENU_OBJECTS, new ArrayList<>(menuObjects)); - contextMenuDialogFragment.setArguments(args); - return contextMenuDialogFragment; - } - - public static ContextMenuDialogFragment newInstance(int actionBarSize, ArrayList menuObjects, int animationDelay) { - ContextMenuDialogFragment contextMenuDialogFragment = new ContextMenuDialogFragment(); - Bundle args = new Bundle(); - args.putInt(ACTION_BAR_SIZE, actionBarSize); - args.putParcelableArrayList(MENU_OBJECTS, menuObjects); - args.putInt(ANIMATION_DELAY, animationDelay); - contextMenuDialogFragment.setArguments(args); - return contextMenuDialogFragment; - } - - public static ContextMenuDialogFragment newInstance(int actionBarSize, ArrayList menuObjects, int animationDelay, int animationDuration) { - ContextMenuDialogFragment contextMenuDialogFragment = new ContextMenuDialogFragment(); - Bundle args = new Bundle(); - args.putInt(ACTION_BAR_SIZE, actionBarSize); - args.putParcelableArrayList(MENU_OBJECTS, menuObjects); - args.putInt(ANIMATION_DELAY, animationDelay); - args.putInt(ANIMATION_DURATION, animationDuration); - contextMenuDialogFragment.setArguments(args); - return contextMenuDialogFragment; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mItemClickListener = (ItemClickListener) activity; - } catch (ClassCastException e) { - Log.e(getClass().getName(), "Should implement ItemClickListener"); - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setStyle(STYLE_NO_FRAME, R.style.MenuFragmentStyle); - if (getArguments() != null) { - mActionBarHeight = getArguments().getInt(ACTION_BAR_SIZE); - mMenuObjects = getArguments().getParcelableArrayList(MENU_OBJECTS); - if(getArguments().containsKey(ANIMATION_DELAY)){ - mAnimationDelay = getArguments().getInt(ANIMATION_DELAY); - } - mAnimationDuration = (getArguments().containsKey(ANIMATION_DELAY))? - getArguments().getInt(ANIMATION_DELAY): MenuAdapter.ANIMATION_DURATION_MILLIS; - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.fragment_menu, container, false); - initViews(rootView); - getDialog().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - initDropDownMenuAdapter(); - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - mDropDownMenuAdapter.menuToggle(); - } - },mAnimationDelay); - return rootView; - } - - private void initViews(View view) { - mWrapperButtons = (LinearLayout) view.findViewById(R.id.wrapper_buttons); - mWrapperText = (LinearLayout) view.findViewById(R.id.wrapper_text); - } - - private void initDropDownMenuAdapter() { - mDropDownMenuAdapter = new MenuAdapter(getActivity(), mWrapperButtons, mWrapperText, mMenuObjects, mActionBarHeight, this); - mDropDownMenuAdapter.setAnimationDuration(mAnimationDuration); - } - - /** - * Menu item click method - */ - @Override - public void onClick(View v) { - mItemClickListener.onItemClick(v, mWrapperButtons.indexOfChild(v)); - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - dismiss(); - } - },mAnimationDelay); - } -} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/ContextMenuDialogFragment.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/ContextMenuDialogFragment.kt new file mode 100644 index 0000000..d7964f6 --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/ContextMenuDialogFragment.kt @@ -0,0 +1,111 @@ +package com.yalantis.contextmenu.lib + +import android.os.Bundle +import android.os.Handler +import android.support.v4.app.DialogFragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import com.yalantis.contextmenu.lib.extensions.backgroundColorAppear +import com.yalantis.contextmenu.lib.extensions.backgroundColorDisappear +import kotlinx.android.synthetic.main.fragment_menu.* + +open class ContextMenuDialogFragment : DialogFragment() { + + var menuItemClickListener: (view: View, position: Int) -> Unit = { _, _ -> } + var menuItemLongClickListener: (view: View, position: Int) -> Unit = { _, _ -> } + + private lateinit var menuParams: MenuParams + private lateinit var dropDownMenuAdapter: MenuAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_FRAME, R.style.MenuFragmentStyle) + menuParams = arguments?.getParcelable(ARGS_MENU_PARAMS) as? MenuParams ?: MenuParams() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.fragment_menu, container, false)?.apply { + fitsSystemWindows = menuParams.isFitsSystemWindow + (this as ViewGroup).clipToPadding = menuParams.isClipToPadding + dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + initDropDownMenuAdapter() + + Handler().postDelayed({ + dropDownMenuAdapter.menuToggle() + }, menuParams.animationDelay) + + wrapperView.apply { + backgroundColorAppear(menuParams.backgroundColorAnimationDuration) + show(menuParams.gravity) + + if (menuParams.isClosableOutside) { + rootRelativeLayout.setOnClickListener { + if (isAdded) { + dropDownMenuAdapter.closeOutside() + } + } + } + } + } + + private fun initDropDownMenuAdapter() { + activity?.let { + dropDownMenuAdapter = MenuAdapter( + it, + wrapperView.wrapperButtons, + wrapperView.wrapperText, + menuParams.menuObjects, + menuParams.actionBarSize, + menuParams.gravity + ).apply { + setAnimationDuration(menuParams.animationDuration) + + onCloseOutsideClickListener = { _ -> + close() + } + + onItemClickListener = { view -> + val position = (view.parent as ViewGroup).indexOfChild(view) + menuItemClickListener(view, position) + close() + } + + onItemLongClickListener = { view -> + val position = (view.parent as ViewGroup).indexOfChild(view) + menuItemLongClickListener(view, position) + close() + } + } + } + } + + private fun close() { + wrapperView.backgroundColorDisappear(menuParams.backgroundColorAnimationDuration) { + Handler().postDelayed({ + dismissAllowingStateLoss() + }, menuParams.animationDelay) + } + } + + companion object { + + const val TAG = "ContextMenuDialogFragment" + private const val ARGS_MENU_PARAMS = "menuParams" + + @JvmStatic + fun newInstance(menuParams: MenuParams): ContextMenuDialogFragment = + ContextMenuDialogFragment().apply { + arguments = Bundle().apply { + putParcelable(ARGS_MENU_PARAMS, menuParams) + } + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/HesitateInterpolator.java b/lib/src/main/java/com/yalantis/contextmenu/lib/HesitateInterpolator.java deleted file mode 100644 index 8769fef..0000000 --- a/lib/src/main/java/com/yalantis/contextmenu/lib/HesitateInterpolator.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.yalantis.contextmenu.lib; - -import android.view.animation.Interpolator; - -public class HesitateInterpolator implements Interpolator { - - public HesitateInterpolator() { - } - - public float getInterpolation(float t) { - float x = 2.0f * t - 1.0f; - return 0.5f * (x * x * x + 1.0f); - } -} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/HesitateInterpolator.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/HesitateInterpolator.kt new file mode 100644 index 0000000..ba4f6f1 --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/HesitateInterpolator.kt @@ -0,0 +1,11 @@ +package com.yalantis.contextmenu.lib + +import android.view.animation.Interpolator + +class HesitateInterpolator : Interpolator { + + override fun getInterpolation(input: Float): Float { + val x = 2.0f * input - 1.0f + return 0.5f * (x * x * x + 1.0f) + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/MenuAdapter.java b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuAdapter.java deleted file mode 100644 index 59a1200..0000000 --- a/lib/src/main/java/com/yalantis/contextmenu/lib/MenuAdapter.java +++ /dev/null @@ -1,305 +0,0 @@ -package com.yalantis.contextmenu.lib; - -import android.content.Context; -import android.view.View; -import android.widget.LinearLayout; - -import com.nineoldandroids.animation.Animator; -import com.nineoldandroids.animation.AnimatorSet; -import com.nineoldandroids.animation.ObjectAnimator; -import com.nineoldandroids.view.ViewHelper; - -import java.util.ArrayList; -import java.util.List; - -public class MenuAdapter { - - public static final int ANIMATION_DURATION_MILLIS = 100; - - private OnItemClickListener mOnItemClickListener; - private Context mContext; - private LinearLayout mMenuWrapper; - private LinearLayout mTextWrapper; - private View mClickedView; - private List mMenuObjects; - private AnimatorSet mAnimatorSetHideMenu; - private AnimatorSet mAnimatorSetShowMenu; - private boolean mIsMenuOpen = false; - private boolean mIsAnimationRun = false; - private int mMenuItemSize; - private int mAnimationDurationMilis = ANIMATION_DURATION_MILLIS; - - - public interface OnItemClickListener { - public void onClick(View v); - } - - public MenuAdapter(Context context, LinearLayout menuWrapper, LinearLayout textWrapper, List menuObjects, - int actionBarHeight, OnItemClickListener onItemClickListener) { - this.mContext = context; - this.mMenuWrapper = menuWrapper; - this.mTextWrapper = textWrapper; - this.mMenuObjects = menuObjects; - this.mOnItemClickListener = onItemClickListener; - -/** - / Make menu looks better by setting toolbar height as itemSize. - */ - this.mMenuItemSize = actionBarHeight; - setViews(); - resetAnimations(); - mAnimatorSetShowMenu = setOpenCloseAnimation(false); - mAnimatorSetHideMenu = setOpenCloseAnimation(true); - } - - public int getItemCount() { - return mMenuObjects.size(); - } - - /** - * Creating views and filling to wrappers - */ - private void setViews() { - for (MenuObject menuObject : mMenuObjects) { - mTextWrapper.addView(Utils.getItemTextView(mContext, menuObject.getTitle(), mMenuItemSize)); - mMenuWrapper.addView(Utils.getImageWrapper(mContext, mMenuItemSize, menuObject.getId(), clickItem)); - } - } - - /** - * Set starting params to vertical animations - */ - private void resetVerticalAnimation(View view, boolean toTop) { - if (!mIsMenuOpen) { - ViewHelper.setRotation(view, 0); - ViewHelper.setRotationY(view, 0); - ViewHelper.setRotationX(view, -90); - } - ViewHelper.setPivotX(view, mMenuItemSize / 2); - ViewHelper.setPivotY(view, !toTop ? 0 : mMenuItemSize); - } - - /** - * Set starting params to side animations - */ - private void resetSideAnimation(View view) { - if (!mIsMenuOpen) { - ViewHelper.setRotation(view, 0); - ViewHelper.setRotationY(view, -90); - ViewHelper.setRotationX(view, 0); - } - ViewHelper.setPivotX(view, mMenuItemSize); - ViewHelper.setPivotY(view, mMenuItemSize / 2); - } - - /** - * Set starting params to text animations - */ - private void resetTextAnimation(View v) { - ViewHelper.setAlpha(v, !mIsMenuOpen ? 0 : 1); - ViewHelper.setTranslationX(v, !mIsMenuOpen ? mMenuItemSize : 0); - } - - /** - * Set starting params to all animations - */ - private void resetAnimations() { - for (int i = 0; i < getItemCount(); i++) { - resetTextAnimation(mTextWrapper.getChildAt(i)); - if (i == 0) { - resetSideAnimation(mMenuWrapper.getChildAt(i)); - } else { - resetVerticalAnimation(mMenuWrapper.getChildAt(i), false); - } - } - } - - /** - * Creates Open / Close AnimatorSet - */ - private AnimatorSet setOpenCloseAnimation(boolean isCloseAnimation) { - List textAnimations = new ArrayList<>(); - List imageAnimations = new ArrayList<>(); - - if (isCloseAnimation) { - for (int i = getItemCount() - 1; i >= 0; i--) { - fillOpenClosingAnimations(true, textAnimations, imageAnimations, i); - } - } else { - for (int i = 0; i < getItemCount(); i++) { - fillOpenClosingAnimations(false, textAnimations, imageAnimations, i); - } - } - - AnimatorSet textCloseAnimatorSet = new AnimatorSet(); - textCloseAnimatorSet.playSequentially(textAnimations); - - AnimatorSet imageCloseAnimatorSet = new AnimatorSet(); - imageCloseAnimatorSet.playSequentially(imageAnimations); - - AnimatorSet animatorFullSet = new AnimatorSet(); - animatorFullSet.playTogether(imageCloseAnimatorSet, textCloseAnimatorSet); - animatorFullSet.setDuration(mAnimationDurationMilis); - animatorFullSet.addListener(mCloseOpenAnimatorListener); - animatorFullSet.setStartDelay(0); - animatorFullSet.setInterpolator(new HesitateInterpolator()); - return animatorFullSet; - } - - /** - * Filling arrays of animations to build Set of Closing / Opening animations - */ - private void fillOpenClosingAnimations(boolean isCloseAnimation, List textAnimations, List imageAnimations, int wrapperPosition) { - AnimatorSet textAnimatorSet = new AnimatorSet(); - Animator textAppearance = isCloseAnimation ? - AnimatorUtils.alfaDisappear(mTextWrapper.getChildAt(wrapperPosition)) - : AnimatorUtils.alfaAppear(mTextWrapper.getChildAt(wrapperPosition)); - - Animator textTranslation = isCloseAnimation ? - AnimatorUtils.translationRight(mTextWrapper.getChildAt(wrapperPosition), mContext.getResources().getDimension(R.dimen.text_right_translation)) - : AnimatorUtils.translationLeft(mTextWrapper.getChildAt(wrapperPosition), mContext.getResources().getDimension(R.dimen.text_right_translation)); - - textAnimatorSet.playTogether(textAppearance, textTranslation); - textAnimations.add(textAnimatorSet); - - Animator imageRotation = isCloseAnimation ? - wrapperPosition == 0 ? AnimatorUtils.rotationCloseToRight(mMenuWrapper.getChildAt(wrapperPosition)) : AnimatorUtils.rotationCloseVertical(mMenuWrapper.getChildAt(wrapperPosition)) - : wrapperPosition == 0 ? AnimatorUtils.rotationOpenFromRight(mMenuWrapper.getChildAt(wrapperPosition)) : AnimatorUtils.rotationOpenVertical(mMenuWrapper.getChildAt(wrapperPosition)); - imageAnimations.add(imageRotation); - } - - private View.OnClickListener clickItem = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mIsMenuOpen && !mIsAnimationRun) { - mClickedView = v; - int childIndex = mMenuWrapper.indexOfChild(v); - if (childIndex == -1) { - return; - } - toggleIsAnimationRun(); - buildChosenAnimation(childIndex); - toggleIsMenuOpen(); - } - } - }; - - /** - * Builds and runs chosen item and menu closing animation - */ - private void buildChosenAnimation(int childIndex) { - List fadeOutTextTopAnimatorList = new ArrayList<>(); - List closeToBottomImageAnimatorList = new ArrayList<>(); - for (int i = 0; i < childIndex; i++) { - View view = mMenuWrapper.getChildAt(i); - resetVerticalAnimation(view, true); - closeToBottomImageAnimatorList.add(AnimatorUtils.rotationCloseVertical(view)); - fadeOutTextTopAnimatorList.add(AnimatorUtils.fadeOutSet(mTextWrapper.getChildAt(i), mContext.getResources().getDimension(R.dimen.text_right_translation))); - } - AnimatorSet closeToBottom = new AnimatorSet(); - closeToBottom.playSequentially(closeToBottomImageAnimatorList); - AnimatorSet fadeOutTop = new AnimatorSet(); - fadeOutTop.playSequentially(fadeOutTextTopAnimatorList); - - List fadeOutTextBottomAnimatorList = new ArrayList<>(); - List closeToTopAnimatorObjects = new ArrayList<>(); - for (int i = getItemCount() - 1; i > childIndex; i--) { - View view = mMenuWrapper.getChildAt(i); - resetVerticalAnimation(view, false); - closeToTopAnimatorObjects.add(AnimatorUtils.rotationCloseVertical(view)); - fadeOutTextBottomAnimatorList.add(AnimatorUtils.fadeOutSet(mTextWrapper.getChildAt(i), mContext.getResources().getDimension(R.dimen.text_right_translation))); - } - AnimatorSet closeToTop = new AnimatorSet(); - closeToTop.playSequentially(closeToTopAnimatorObjects); - AnimatorSet fadeOutBottom = new AnimatorSet(); - fadeOutBottom.playSequentially(fadeOutTextBottomAnimatorList); - - resetSideAnimation(mMenuWrapper.getChildAt(childIndex)); - ObjectAnimator closeToRight = AnimatorUtils.rotationCloseToRight(mMenuWrapper.getChildAt(childIndex)); - closeToRight.addListener(mChosenItemFinishAnimatorListener); - AnimatorSet fadeOutChosenText = AnimatorUtils.fadeOutSet(mTextWrapper.getChildAt(childIndex), mContext.getResources().getDimension(R.dimen.text_right_translation)); - - AnimatorSet imageFullAnimatorSet = new AnimatorSet(); - imageFullAnimatorSet.play(closeToBottom).with(closeToTop); - AnimatorSet textFullAnimatorSet = new AnimatorSet(); - textFullAnimatorSet.play(fadeOutTop).with(fadeOutBottom); - if (closeToBottomImageAnimatorList.size() >= closeToTopAnimatorObjects.size()) { - imageFullAnimatorSet.play(closeToBottom).before(closeToRight); - textFullAnimatorSet.play(fadeOutTop).before(fadeOutChosenText); - } else { - imageFullAnimatorSet.play(closeToTop).before(closeToRight); - textFullAnimatorSet.play(fadeOutBottom).before(fadeOutChosenText); - } - - AnimatorSet fullAnimatorSet = new AnimatorSet(); - fullAnimatorSet.playTogether(imageFullAnimatorSet, textFullAnimatorSet); - fullAnimatorSet.setDuration(mAnimationDurationMilis); - fullAnimatorSet.setInterpolator(new HesitateInterpolator()); - fullAnimatorSet.start(); - } - - public void menuToggle() { - if (!mIsAnimationRun) { - resetAnimations(); - mIsAnimationRun = true; - if (mIsMenuOpen) { - mAnimatorSetHideMenu.start(); - } else { - mAnimatorSetShowMenu.start(); - } - toggleIsMenuOpen(); - } - } - - private void toggleIsAnimationRun() { - mIsAnimationRun = !mIsAnimationRun; - } - - private void toggleIsMenuOpen() { - mIsMenuOpen = !mIsMenuOpen; - } - - public void setAnimationDuration(int durationMillis){ - mAnimationDurationMilis = durationMillis; - } - - private Animator.AnimatorListener mCloseOpenAnimatorListener = new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - toggleIsAnimationRun(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }; - - private Animator.AnimatorListener mChosenItemFinishAnimatorListener = new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - toggleIsAnimationRun(); - mOnItemClickListener.onClick(mClickedView); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }; - -} diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/MenuAdapter.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuAdapter.kt new file mode 100644 index 0000000..794d6de --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuAdapter.kt @@ -0,0 +1,415 @@ +package com.yalantis.contextmenu.lib + +import android.animation.Animator +import android.animation.AnimatorSet +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import com.yalantis.contextmenu.lib.extensions.* + +open class MenuAdapter( + private val context: Context, + private val menuWrapper: LinearLayout, + private val textWrapper: LinearLayout, + private val menuObjects: List, + private val actionBarSize: Int, + private val gravity: MenuGravity +) { + + var onCloseOutsideClickListener: (view: View) -> Unit = {} + var onItemClickListener: (view: View) -> Unit = {} + var onItemLongClickListener: (view: View) -> Unit = {} + private var onItemClickListenerCalled: (view: View) -> Unit = {} + private var onItemLongClickListenerCalled: (view: View) -> Unit = {} + + private var clickedView: View? = null + + private val hideMenuAnimatorSet: AnimatorSet by lazy { setOpenCloseAnimation(true) } + private val showMenuAnimatorSet: AnimatorSet by lazy { setOpenCloseAnimation(false) } + + private var isMenuOpen = false + private var isAnimationRun = false + + private var animationDuration = MenuParams.ANIMATION_DURATION + + private val itemClickListener = View.OnClickListener { view -> + onItemClickListenerCalled = onItemClickListener + viewClicked(view) + } + + private val itemLongClickListener = View.OnLongClickListener { view -> + onItemLongClickListenerCalled = onItemLongClickListener + viewClicked(view) + true + } + + init { + setViews() + } + + open fun setAnimationDuration(duration: Long) { + animationDuration = duration + showMenuAnimatorSet.duration = animationDuration + hideMenuAnimatorSet.duration = animationDuration + } + + fun menuToggle() { + if (!isAnimationRun) { + resetAnimations() + isAnimationRun = true + + if (isMenuOpen) { + hideMenuAnimatorSet.start() + } else { + showMenuAnimatorSet.start() + } + + toggleIsMenuOpen() + } + } + + fun closeOutside() { + onItemClickListenerCalled = onCloseOutsideClickListener + menuWrapper.getChildAt(FIRST_CHILD_INDEX)?.let { + viewClicked(it) + } + } + + fun getItemCount() = menuObjects.size + + private fun getLastItemPosition() = getItemCount() - 1 + + /** + * Creating views and filling to wrappers + */ + private fun setViews() { + menuObjects.forEachIndexed { index, menuObject -> + context.apply { + textWrapper.addView( + getItemTextView( + menuObject, + actionBarSize, + itemClickListener, + itemLongClickListener + ) + ) + menuWrapper.addView( + getImageWrapper( + menuObject, + actionBarSize, + itemClickListener, + itemLongClickListener, + index != getLastItemPosition() + ) + ) + } + } + } + + /** + * Set starting params to vertical animations + */ + private fun resetVerticalAnimation(view: View, toTop: Boolean) { + view.apply { + if (!isMenuOpen) { + rotation = ROTATION_ZERO_DEGREES + rotationY = ROTATION_ZERO_DEGREES + rotationX = -ROTATION_NINETY_DEGREES + } + + pivotX = (actionBarSize / 2).toFloat() + pivotY = (if (!toTop) 0 else actionBarSize).toFloat() + } + } + + /** + * Set starting params to side animations + */ + private fun resetSideAnimation(view: View) { + view.apply { + if (!isMenuOpen) { + rotation = ROTATION_ZERO_DEGREES + rotationY = this@MenuAdapter.getRotationY() + rotationX = ROTATION_ZERO_DEGREES + } + + pivotX = this@MenuAdapter.getPivotX() + pivotY = (actionBarSize / 2).toFloat() + } + } + + private fun getRotationY() = + when (gravity) { + MenuGravity.END -> if (context.isLayoutDirectionRtl()) { + ROTATION_NINETY_DEGREES + } else { + -ROTATION_NINETY_DEGREES + } + MenuGravity.START -> if (context.isLayoutDirectionRtl()) { + -ROTATION_NINETY_DEGREES + } else { + ROTATION_NINETY_DEGREES + } + } + + private fun getPivotX() = + when (gravity) { + MenuGravity.END -> if (context.isLayoutDirectionRtl()) { + ROTATION_ZERO_DEGREES + } else { + actionBarSize.toFloat() + } + MenuGravity.START -> if (context.isLayoutDirectionRtl()) { + actionBarSize.toFloat() + } else { + ROTATION_ZERO_DEGREES + } + } + + /** + * Set starting params to text animations + */ + private fun resetTextAnimation(view: View) { + view.apply { + alpha = if (!isMenuOpen) ALPHA_INVISIBLE else ALPHA_VISIBLE + translationX = if (!isMenuOpen) actionBarSize.toFloat() else TRANSLATION_ZERO_VALUE + } + } + + /** + * Set starting params to all animations + */ + private fun resetAnimations() { + for (i in 0 until getItemCount()) { + resetTextAnimation(textWrapper.getChildAt(i)) + + if (i == 0) { + resetSideAnimation(menuWrapper.getChildAt(i)) + } else { + resetVerticalAnimation(menuWrapper.getChildAt(i), false) + } + } + } + + /** + * Creates open/close AnimatorSet + */ + private fun setOpenCloseAnimation(isCloseAnimation: Boolean): AnimatorSet { + val textAnimations = mutableListOf() + val imageAnimations = mutableListOf() + + if (isCloseAnimation) { + for (i in getLastItemPosition() downTo 0) { + fillOpenClosingAnimations(true, textAnimations, imageAnimations, i) + } + } else { + for (i in 0 until getItemCount()) { + fillOpenClosingAnimations(false, textAnimations, imageAnimations, i) + } + } + + return AnimatorSet().apply { + duration = animationDuration + startDelay = 0 + interpolator = HesitateInterpolator() + + playTogether( + AnimatorSet().apply { playSequentially(textAnimations) }, + AnimatorSet().apply { playSequentially(imageAnimations) } + ) + onAnimationEnd { toggleIsAnimationRun() } + } + } + + /** + * Filling arrays of animations to build Set of Closing / Opening animations + */ + private fun fillOpenClosingAnimations( + isCloseAnimation: Boolean, + textAnimations: MutableList, + imageAnimations: MutableList, + wrapperPosition: Int + ) { + textWrapper.getChildAt(wrapperPosition).apply { + textAnimations.add(AnimatorSet().apply { + val textAppearance = if (isCloseAnimation) { + alphaDisappear() + } else { + alphaAppear() + } + + val textTranslation = if (isCloseAnimation) { + when (gravity) { + MenuGravity.END -> translationEnd(getTextEndTranslation()) + MenuGravity.START -> translationStart(getTextEndTranslation()) + } + } else { + when (gravity) { + MenuGravity.END -> translationStart(getTextEndTranslation()) + MenuGravity.START -> translationEnd(getTextEndTranslation()) + } + } + + playTogether(textAppearance, textTranslation) + }) + } + + menuWrapper.getChildAt(wrapperPosition).apply { + imageAnimations.add( + if (isCloseAnimation) { + if (wrapperPosition == 0) { + rotationCloseHorizontal(gravity) + } else { + rotationCloseVertical() + } + } else { + if (wrapperPosition == 0) { + rotationOpenHorizontal(gravity) + } else { + rotationOpenVertical() + } + } + ) + } + } + + private fun viewClicked(view: View) { + if (isMenuOpen && !isAnimationRun) { + clickedView = view + + val childIndex = (view.parent as ViewGroup).indexOfChild(view) + if (childIndex == -1) { + return + } + + toggleIsAnimationRun() + buildChosenAnimation(childIndex) + toggleIsMenuOpen() + } + } + + private fun buildChosenAnimation(childIndex: Int) { + val fadeOutTextTopAnimatorList = mutableListOf() + val closeToBottomImageAnimatorList = mutableListOf() + val fadeOutTextBottomAnimatorList = mutableListOf() + val closeToTopImageAnimatorList = mutableListOf() + + fillAnimatorLists( + childIndex, + fadeOutTextTopAnimatorList, + closeToBottomImageAnimatorList, + fadeOutTextBottomAnimatorList, + closeToTopImageAnimatorList + ) + + resetSideAnimation(menuWrapper.getChildAt(childIndex)) + + val fullAnimatorSetPair = getFullAnimatorSetPair( + childIndex, + fadeOutTextTopAnimatorList, + closeToBottomImageAnimatorList, + fadeOutTextBottomAnimatorList, + closeToTopImageAnimatorList + ) + + AnimatorSet().apply { + duration = animationDuration + interpolator = HesitateInterpolator() + playTogether(fullAnimatorSetPair.first, fullAnimatorSetPair.second) + start() + } + } + + private fun fillAnimatorLists( + childIndex: Int, + fadeOutTextTopAnimatorList: MutableList, + closeToBottomImageAnimatorList: MutableList, + fadeOutTextBottomAnimatorList: MutableList, + closeToTopImageAnimatorList: MutableList + ) { + for (i in 0..getLastItemPosition()) { + val menuWrapperChild = menuWrapper.getChildAt(i) + val menuWrapperChildRotation = menuWrapperChild.rotationCloseVertical() + val textWrapperChildFadeOut = + textWrapper.getChildAt(i).fadeOutSet(getTextEndTranslation(), gravity) + + when (i) { + in 0 until childIndex -> { + resetVerticalAnimation(menuWrapperChild, true) + closeToBottomImageAnimatorList.add(menuWrapperChildRotation) + fadeOutTextTopAnimatorList.add(textWrapperChildFadeOut) + } + in childIndex + 1..getLastItemPosition() -> { + resetVerticalAnimation(menuWrapperChild, false) + closeToTopImageAnimatorList.add(menuWrapperChildRotation) + fadeOutTextBottomAnimatorList.add(textWrapperChildFadeOut) + } + } + } + + closeToTopImageAnimatorList.reverse() + fadeOutTextBottomAnimatorList.reverse() + } + + private fun getFullAnimatorSetPair( + childIndex: Int, + fadeOutTextTopAnimatorList: MutableList, + closeToBottomImageAnimatorList: MutableList, + fadeOutTextBottomAnimatorList: MutableList, + closeToTopImageAnimatorList: MutableList + ): Pair { + val closeToBottom = AnimatorSet() + closeToBottom.playSequentially(closeToBottomImageAnimatorList) + val fadeOutTop = AnimatorSet() + fadeOutTop.playSequentially(fadeOutTextTopAnimatorList) + + val closeToTop = AnimatorSet() + closeToTop.playSequentially(closeToTopImageAnimatorList) + val fadeOutBottom = AnimatorSet() + fadeOutBottom.playSequentially(fadeOutTextBottomAnimatorList) + + val closeToEnd = menuWrapper.getChildAt(childIndex).rotationCloseHorizontal(gravity) + closeToEnd.onAnimationEnd { + toggleIsAnimationRun() + + clickedView?.let { notNullView -> + onItemClickListenerCalled(notNullView) + onItemLongClickListenerCalled(notNullView) + } + } + val fadeOutChosenText = + textWrapper.getChildAt(childIndex).fadeOutSet(getTextEndTranslation(), gravity) + + val imageFullAnimatorSet = AnimatorSet() + imageFullAnimatorSet.play(closeToBottom).with(closeToTop) + val textFullAnimatorSet = AnimatorSet() + textFullAnimatorSet.play(fadeOutTop).with(fadeOutBottom) + + if (closeToBottomImageAnimatorList.size >= closeToTopImageAnimatorList.size) { + imageFullAnimatorSet.play(closeToBottom).before(closeToEnd) + textFullAnimatorSet.play(fadeOutTop).before(fadeOutChosenText) + } else { + imageFullAnimatorSet.play(closeToTop).before(closeToEnd) + textFullAnimatorSet.play(fadeOutBottom).before(fadeOutChosenText) + } + + return Pair(imageFullAnimatorSet, textFullAnimatorSet) + } + + private fun getTextEndTranslation() = + context.getDimension(R.dimen.text_translation).toFloat() + + private fun toggleIsAnimationRun() { + isAnimationRun = !isAnimationRun + } + + private fun toggleIsMenuOpen() { + isMenuOpen = !isMenuOpen + } + + companion object { + + private const val FIRST_CHILD_INDEX = 0 + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/MenuGravity.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuGravity.kt new file mode 100644 index 0000000..f2ae3ec --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuGravity.kt @@ -0,0 +1,5 @@ +package com.yalantis.contextmenu.lib + +enum class MenuGravity { + END, START +} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/MenuObject.java b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuObject.java deleted file mode 100644 index 6c7a492..0000000 --- a/lib/src/main/java/com/yalantis/contextmenu/lib/MenuObject.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.yalantis.contextmenu.lib; - -import android.os.Parcel; -import android.os.Parcelable; - -public class MenuObject implements Parcelable { - - private String mTitle; - private int mId; - - public MenuObject(int id, String title) { - this.mId = id; - this.mTitle = title; - } - - public MenuObject(int id) { - this.mId = id; - this.mTitle = ""; - } - - private MenuObject(Parcel in) { - mTitle = in.readString(); - mId = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mTitle); - dest.writeInt(mId); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public MenuObject createFromParcel(Parcel in) { - return new MenuObject(in); - } - - @Override - public MenuObject[] newArray(int size) { - return new MenuObject[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - public String getTitle() { - return mTitle; - } - - public void setTitle(String title) { - this.mTitle = title; - } - - public int getId() { - return mId; - } - - public void setId(int mId) { - this.mId = mId; - } - -// TODO: Realize drawable constructor -// private Drawable mDrawable; -// public MenuObject(Drawable drawable, String title) { -// this.mDrawable = drawable; -// this.mTitle = title; -// } -// -// public MenuObject(Drawable drawable) { -// this.mDrawable = drawable; -// this.mTitle = ""; -// } -// public Drawable getDrawable() { -// return mDrawable; -// } -// -// public void setDrawable(Drawable drawable) { -// this.mDrawable = drawable; -// } - -} diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/MenuObject.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuObject.kt new file mode 100644 index 0000000..e737697 --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuObject.kt @@ -0,0 +1,144 @@ +package com.yalantis.contextmenu.lib + +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.os.Parcel +import android.os.Parcelable +import android.widget.ImageView + +open class MenuObject(var title: String = "") : Parcelable { + + var bgDrawable: Drawable? = null + + var bgColor: Int = 0 + private set + + var bgResource: Int = 0 + private set + + var drawable: Drawable? = null + + var color: Int = 0 + private set + + var bitmap: Bitmap? = null + private set + + var resource: Int = 0 + private set + + var scaleType: ImageView.ScaleType = ImageView.ScaleType.CENTER_INSIDE + var textColor: Int = 0 + var dividerColor: Int = Integer.MAX_VALUE + var menuTextAppearanceStyle: Int = 0 + + private constructor(parcel: Parcel) : this(parcel.readString() ?: "") { + val bitmapBgDrawable = parcel.readParcelable(Bitmap::class.java.classLoader) + bgDrawable = if (bitmapBgDrawable == null) { + ColorDrawable(parcel.readInt()) + } else { + // TODO create BitmapDrawable with resources + BitmapDrawable(bitmapBgDrawable) + } + + bgColor = parcel.readInt() + bgResource = parcel.readInt() + + val bitmapDrawable = parcel.readParcelable(Bitmap::class.java.classLoader) + // TODO create BitmapDrawable with resources + bitmapDrawable?.let { drawable = BitmapDrawable(it) } + + color = parcel.readInt() + bitmap = parcel.readParcelable(Bitmap::class.java.classLoader) + resource = parcel.readInt() + scaleType = ImageView.ScaleType.values()[parcel.readInt()] + textColor = parcel.readInt() + dividerColor = parcel.readInt() + menuTextAppearanceStyle = parcel.readInt() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.apply { + writeString(title) + + when (bgDrawable) { + null -> writeParcelable(null, flags) + is BitmapDrawable -> writeParcelable((bgDrawable as BitmapDrawable).bitmap, flags) + is ColorDrawable -> writeInt((bgDrawable as ColorDrawable).color) + } + + writeInt(bgColor) + writeInt(bgResource) + + when (drawable) { + null -> writeParcelable(null, flags) + is BitmapDrawable -> writeParcelable((drawable as BitmapDrawable).bitmap, flags) + } + + writeInt(color) + writeParcelable(bitmap, flags) + writeInt(resource) + writeInt(scaleType.ordinal) + writeInt(textColor) + writeInt(dividerColor) + writeInt(menuTextAppearanceStyle) + } + } + + override fun describeContents(): Int = 0 + + fun setBgDrawable(drawable: ColorDrawable) { + setBgDrawableInternal(drawable) + } + + fun setBgDrawable(drawable: BitmapDrawable) { + setBgDrawableInternal(drawable) + } + + fun setBgColorValue(value: Int) { + bgColor = value + bgDrawable = null + bgResource = 0 + } + + fun setBgResourceValue(value: Int) { + bgResource = value + bgDrawable = null + bgColor = 0 + } + + fun setColorValue(value: Int) { + color = value + drawable = null + bitmap = null + resource = 0 + } + + fun setBitmapValue(value: Bitmap) { + bitmap = value + drawable = null + color = 0 + resource = 0 + } + + fun setResourceValue(value: Int) { + resource = value + drawable = null + color = 0 + bitmap = null + } + + private fun setBgDrawableInternal(drawable: Drawable) { + bgDrawable = drawable + bgColor = 0 + bgResource = 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MenuObject = MenuObject(parcel) + + override fun newArray(size: Int): Array = arrayOfNulls(size) + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/MenuParams.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuParams.kt new file mode 100644 index 0000000..04f72f8 --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/MenuParams.kt @@ -0,0 +1,70 @@ +package com.yalantis.contextmenu.lib + +import android.os.Parcel +import android.os.Parcelable + +/** + * @property [animationDelay] + * Delay after opening and before closing. + * @see ContextMenuDialogFragment + * + * @property [isClosableOutside] + * If option menu can be closed on touch to non-button area. + * + * @property [gravity] + * You can change the side. By default - MenuGravity.END + * @see MenuGravity + */ +data class MenuParams( + var actionBarSize: Int = 0, + var menuObjects: List = listOf(), + var animationDelay: Long = 0L, + var animationDuration: Long = ANIMATION_DURATION, + var backgroundColorAnimationDuration: Long = BACKGROUND_COLOR_ANIMATION_DURATION, + var isFitsSystemWindow: Boolean = false, + var isClipToPadding: Boolean = true, + var isClosableOutside: Boolean = false, + var gravity: MenuGravity = MenuGravity.END +) : Parcelable { + + private constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.createTypedArrayList(MenuObject.CREATOR) ?: listOf(), + parcel.readLong(), + parcel.readLong(), + parcel.readLong(), + parcel.readByte() != 0.toByte(), + parcel.readByte() != 0.toByte(), + parcel.readByte() != 0.toByte(), + parcel.readSerializable() as? MenuGravity ?: MenuGravity.END + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.apply { + writeInt(actionBarSize) + writeTypedList(menuObjects) + writeLong(animationDelay) + writeLong(animationDuration) + writeLong(backgroundColorAnimationDuration) + writeByte(if (isFitsSystemWindow) 1 else 0) + writeByte(if (isClipToPadding) 1 else 0) + writeByte(if (isClosableOutside) 1 else 0) + writeSerializable(gravity) + } + } + + override fun describeContents(): Int = 0 + + companion object { + + const val ANIMATION_DURATION = 100L + const val BACKGROUND_COLOR_ANIMATION_DURATION = 200L + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MenuParams = MenuParams(parcel) + + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/Utils.java b/lib/src/main/java/com/yalantis/contextmenu/lib/Utils.java deleted file mode 100644 index e0d3e6a..0000000 --- a/lib/src/main/java/com/yalantis/contextmenu/lib/Utils.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.yalantis.contextmenu.lib; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; - -public class Utils { - - public static int getDefaultActionBarSize(Context context) { - final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes( - new int[]{android.R.attr.actionBarSize}); - int actionBarSize = (int) styledAttributes.getDimension(0, 0); - styledAttributes.recycle(); - return actionBarSize; - } - - public static TextView getItemTextView(Context context, String title, int menuItemSize) { - TextView itemTextView = new TextView(context); - RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, menuItemSize); - itemTextView.setLayoutParams(textLayoutParams); - itemTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.menu_text_size)); - itemTextView.setTextColor(context.getResources().getColor(android.R.color.white)); - itemTextView.setText(title); - itemTextView.setPadding(0, 0, (int) context.getResources().getDimension(R.dimen.text_right_padding), 0); - itemTextView.setGravity(Gravity.CENTER_VERTICAL); - return itemTextView; - } - - public static ImageView getItemImageButton(Context context, Drawable drawable) { - ImageView imageView = new ImageButton(context); - RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - lp.addRule(RelativeLayout.CENTER_IN_PARENT); - imageView.setLayoutParams(lp); - imageView.setPadding((int) context.getResources().getDimension(R.dimen.menu_item_padding), - (int) context.getResources().getDimension(R.dimen.menu_item_padding), - (int) context.getResources().getDimension(R.dimen.menu_item_padding), - (int) context.getResources().getDimension(R.dimen.menu_item_padding)); - imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - imageView.setBackgroundColor(context.getResources().getColor(android.R.color.transparent)); - imageView.setImageDrawable(drawable); - imageView.setClickable(false); - imageView.setFocusable(false); - return imageView; - } - - public static View getDivider(Context context) { - View dividerView = new View(context); - RelativeLayout.LayoutParams viewLayoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) context.getResources().getDimension(R.dimen.divider_height)); - viewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); - dividerView.setLayoutParams(viewLayoutParams); - dividerView.setClickable(true); - dividerView.setBackgroundColor(context.getResources().getColor(R.color.divider_color)); - return dividerView; - } - - public static RelativeLayout getImageWrapper(Context context, int menuItemSize, int drawableId, View.OnClickListener onCLick) { - RelativeLayout imageWrapper = new RelativeLayout(context); - LinearLayout.LayoutParams imageWrapperLayoutParams = new LinearLayout.LayoutParams(menuItemSize, menuItemSize); - imageWrapper.setLayoutParams(imageWrapperLayoutParams); - imageWrapper.setBackgroundColor(context.getResources().getColor(R.color.menu_item_background)); - imageWrapper.setOnClickListener(onCLick); - imageWrapper.addView(Utils.getItemImageButton(context, context.getResources().getDrawable(drawableId))); - imageWrapper.addView(getDivider(context)); - return imageWrapper; - } - -} diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/WrapperView.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/WrapperView.kt new file mode 100644 index 0000000..e482335 --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/WrapperView.kt @@ -0,0 +1,102 @@ +package com.yalantis.contextmenu.lib + +import android.content.Context +import android.support.v4.view.ViewCompat +import android.util.AttributeSet +import android.view.Gravity +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.ScrollView +import com.yalantis.contextmenu.lib.extensions.getDimension + +open class WrapperView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ScrollView(context, attrs, defStyleAttr) { + + val rootRelativeLayout: RelativeLayout by lazy { RelativeLayout(context) } + val wrapperButtons: LinearLayout by lazy { LinearLayout(context) } + val wrapperText: LinearLayout by lazy { LinearLayout(context) } + + init { + setupScrollView() + setupRootRelativeLayout() + setupWrappers() + } + + open fun show(menuGravity: MenuGravity) { + wrapperButtons.layoutParams = + RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ).apply { + addRule( + if (menuGravity == MenuGravity.START) { + RelativeLayout.ALIGN_PARENT_START + } else { + RelativeLayout.ALIGN_PARENT_END + } + ) + } + + wrapperText.apply { + gravity = if (menuGravity == MenuGravity.START) { + Gravity.START + } else { + Gravity.END + } + layoutParams = + RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ).apply { + val verb = if (menuGravity == MenuGravity.START) { + RelativeLayout.END_OF + } else { + RelativeLayout.START_OF + } + addRule(verb, wrapperButtons.id) + setMargins( + 0, + 0, + context.getDimension(R.dimen.text_start_end_margin), + 0 + ) + } + } + } + + private fun setupScrollView() { + isFillViewport = true + } + + private fun setupRootRelativeLayout() { + addView( + rootRelativeLayout.apply { + id = ViewCompat.generateViewId() + layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ) + } + ) + } + + private fun setupWrappers() { + rootRelativeLayout.addView( + wrapperButtons.apply { + id = ViewCompat.generateViewId() + orientation = LinearLayout.VERTICAL + } + ) + + rootRelativeLayout.addView( + wrapperText.apply { + id = ViewCompat.generateViewId() + orientation = LinearLayout.VERTICAL + } + ) + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/extensions/Animator.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/extensions/Animator.kt new file mode 100644 index 0000000..b40d492 --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/extensions/Animator.kt @@ -0,0 +1,23 @@ +package com.yalantis.contextmenu.lib.extensions + +import android.animation.Animator + +internal fun Animator.onAnimationEnd(onAnimationEnd: (Animator?) -> Unit) { + this.addListener(object : Animator.AnimatorListener { + override fun onAnimationRepeat(animation: Animator?) { + // do nothing + } + + override fun onAnimationEnd(animation: Animator?) { + onAnimationEnd(animation) + } + + override fun onAnimationCancel(animation: Animator?) { + // do nothing + } + + override fun onAnimationStart(animation: Animator?) { + // do nothing + } + }) +} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/extensions/Context.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/extensions/Context.kt new file mode 100644 index 0000000..f717860 --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/extensions/Context.kt @@ -0,0 +1,140 @@ +package com.yalantis.contextmenu.lib.extensions + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Build +import android.support.annotation.ColorRes +import android.support.annotation.DimenRes +import android.support.v4.content.ContextCompat +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.* +import com.yalantis.contextmenu.lib.MenuObject +import com.yalantis.contextmenu.lib.R + +@JvmName("getDefaultActionBarSize") +fun Context.getDefaultActionBarSize(): Int { + val styledAttrs = theme.obtainStyledAttributes(intArrayOf(android.R.attr.actionBarSize)) + val actionBarSize = styledAttrs.getDimension(0, 0f).toInt() + styledAttrs.recycle() + return actionBarSize +} + +internal fun Context.getColorCompat(@ColorRes color: Int) = ContextCompat.getColor(this, color) + +internal fun Context.getDimension(@DimenRes dimen: Int) = resources.getDimension(dimen).toInt() + +internal fun Context.isLayoutDirectionRtl(): Boolean = + resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL + +internal fun Context.getItemTextView( + menuItem: MenuObject, + menuItemSize: Int, + onCLick: View.OnClickListener, + onLongClick: View.OnLongClickListener +): TextView = TextView(this).apply { + val textColor = if (menuItem.textColor == 0) { + android.R.color.white + } else { + menuItem.textColor + } + + val styleResId = if (menuItem.menuTextAppearanceStyle > 0) { + menuItem.menuTextAppearanceStyle + } else { + R.style.TextView_DefaultStyle + } + + layoutParams = RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + menuItemSize + ) + text = menuItem.title + gravity = Gravity.CENTER_VERTICAL + + setOnClickListener(onCLick) + setOnLongClickListener(onLongClick) + setPadding( + getDimension(R.dimen.text_start_end_padding_medium), + 0, + getDimension(R.dimen.text_start_end_padding_small), + 0 + ) + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + setTextAppearance(context, styleResId) + } else { + setTextAppearance(styleResId) + } + + setTextColor(getColorCompat(textColor)) +} + +internal fun Context.getItemImageButton(menuItem: MenuObject): ImageView = + ImageButton(this).apply { + val paddingValue = getDimension(R.dimen.menu_item_padding) + + layoutParams = RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + isClickable = false + isFocusable = false + scaleType = menuItem.scaleType + + setPadding(paddingValue, paddingValue, paddingValue, paddingValue) + setBackgroundColor(Color.TRANSPARENT) + + menuItem.apply { + when { + color != 0 -> setImageDrawable(ColorDrawable(color)) + resource != 0 -> setImageResource(resource) + bitmap != null -> setImageBitmap(bitmap) + drawable != null -> setImageDrawable(drawable) + } + } + } + +internal fun Context.getDivider(menuItem: MenuObject): View = View(this).apply { + val dividerColor = if (menuItem.dividerColor == Integer.MAX_VALUE) { + R.color.divider_color + } else { + menuItem.dividerColor + } + + layoutParams = RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + getDimension(R.dimen.divider_height) + ).apply { addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) } + isClickable = true + + setBackgroundColor(getColorCompat(dividerColor)) +} + +internal fun Context.getImageWrapper( + menuItem: MenuObject, + menuItemSize: Int, + onCLick: View.OnClickListener, + onLongClick: View.OnLongClickListener, + showDivider: Boolean +): RelativeLayout = RelativeLayout(this).apply { + layoutParams = LinearLayout.LayoutParams(menuItemSize, menuItemSize) + + setOnClickListener(onCLick) + setOnLongClickListener(onLongClick) + addView(getItemImageButton(menuItem)) + if (showDivider) { + addView(getDivider(menuItem)) + } + + menuItem.apply { + when { + bgColor != 0 -> setBackgroundColor(bgColor) + bgDrawable != null -> background = bgDrawable + bgResource != 0 -> setBackgroundResource(bgResource) + else -> setBackgroundColor(getColorCompat(R.color.menu_item_background)) + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/yalantis/contextmenu/lib/extensions/View.kt b/lib/src/main/java/com/yalantis/contextmenu/lib/extensions/View.kt new file mode 100644 index 0000000..3b52deb --- /dev/null +++ b/lib/src/main/java/com/yalantis/contextmenu/lib/extensions/View.kt @@ -0,0 +1,126 @@ +package com.yalantis.contextmenu.lib.extensions + +import android.animation.AnimatorSet +import android.animation.ArgbEvaluator +import android.animation.ObjectAnimator +import android.support.annotation.ColorRes +import android.view.View +import com.yalantis.contextmenu.lib.MenuGravity +import com.yalantis.contextmenu.lib.R + +const val ROTATION_ZERO_DEGREES = 0f +const val ROTATION_NINETY_DEGREES = 90f +const val ALPHA_INVISIBLE = 0f +const val ALPHA_VISIBLE = 1f +const val TRANSLATION_ZERO_VALUE = 0f + +private const val ROTATION_Y_PROPERTY = "rotationY" +private const val ROTATION_X_PROPERTY = "rotationX" +private const val ALPHA_PROPERTY = "alpha" +private const val TRANSLATION_X_PROPERTY = "translationX" +private const val BACKGROUND_COLOR_PROPERTY = "backgroundColor" + +internal fun View.rotationCloseHorizontal(gravity: MenuGravity): ObjectAnimator { + val from = ROTATION_ZERO_DEGREES + var to = when (gravity) { + MenuGravity.END -> -ROTATION_NINETY_DEGREES + MenuGravity.START -> ROTATION_NINETY_DEGREES + } + + if (context.isLayoutDirectionRtl()) { + to *= -1f + } + + return ObjectAnimator.ofFloat(this, ROTATION_Y_PROPERTY, from, to) +} + +internal fun View.rotationOpenHorizontal(gravity: MenuGravity): ObjectAnimator { + var from = when (gravity) { + MenuGravity.END -> -ROTATION_NINETY_DEGREES + MenuGravity.START -> ROTATION_NINETY_DEGREES + } + val to = ROTATION_ZERO_DEGREES + + if (context.isLayoutDirectionRtl()) { + from *= -1f + } + + return ObjectAnimator.ofFloat(this, ROTATION_Y_PROPERTY, from, to) +} + +internal fun View.rotationCloseVertical(): ObjectAnimator = + ObjectAnimator.ofFloat(this, ROTATION_X_PROPERTY, ROTATION_ZERO_DEGREES, -ROTATION_NINETY_DEGREES) + +internal fun View.rotationOpenVertical(): ObjectAnimator = + ObjectAnimator.ofFloat(this, ROTATION_X_PROPERTY, -ROTATION_NINETY_DEGREES, ROTATION_ZERO_DEGREES) + +internal fun View.alphaDisappear(): ObjectAnimator = + ObjectAnimator.ofFloat(this, ALPHA_PROPERTY, ALPHA_VISIBLE, ALPHA_INVISIBLE) + +internal fun View.alphaAppear(): ObjectAnimator = + ObjectAnimator.ofFloat(this, ALPHA_PROPERTY, ALPHA_INVISIBLE, ALPHA_VISIBLE) + +internal fun View.translationEnd(x: Float): ObjectAnimator { + var from = TRANSLATION_ZERO_VALUE + var to = x + + if (context.isLayoutDirectionRtl()) { + from = x + to = TRANSLATION_ZERO_VALUE + } + + return ObjectAnimator.ofFloat(this, TRANSLATION_X_PROPERTY, from, to) +} + +internal fun View.translationStart(x: Float): ObjectAnimator { + var from = x + var to = TRANSLATION_ZERO_VALUE + + if (context.isLayoutDirectionRtl()) { + from = TRANSLATION_ZERO_VALUE + to = x + } + + return ObjectAnimator.ofFloat(this, TRANSLATION_X_PROPERTY, from, to) +} + +internal fun View.fadeOutSet(x: Float, gravity: MenuGravity): AnimatorSet = AnimatorSet().apply { + val translation = when (gravity) { + MenuGravity.END -> translationEnd(x) + MenuGravity.START -> translationStart(x) + } + playTogether(alphaDisappear(), translation) +} + +internal fun View.colorAnimation( + duration: Long, + @ColorRes startColorResId: Int, + @ColorRes endColorResId: Int +): ObjectAnimator = + ObjectAnimator.ofObject( + this, + BACKGROUND_COLOR_PROPERTY, + ArgbEvaluator(), + context.getColorCompat(startColorResId), + context.getColorCompat(endColorResId) + ).apply { + this.duration = duration + start() + } + +internal fun View.backgroundColorAppear(duration: Long): ObjectAnimator = + colorAnimation(duration, android.R.color.transparent, R.color.menu_fragment_background) + +internal fun View.backgroundColorDisappear( + duration: Long, + onAnimationEnd: () -> Unit +): ObjectAnimator = + colorAnimation( + duration, + R.color.menu_fragment_background, + android.R.color.transparent + ).apply { + onAnimationEnd { + onAnimationEnd() + } + } \ No newline at end of file diff --git a/lib/src/main/res/layout/fragment_menu.xml b/lib/src/main/res/layout/fragment_menu.xml index caff7b3..72e7d57 100644 --- a/lib/src/main/res/layout/fragment_menu.xml +++ b/lib/src/main/res/layout/fragment_menu.xml @@ -1,28 +1,5 @@ - + - - - - - - - - - \ No newline at end of file + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/lib/src/main/res/values/dimens.xml b/lib/src/main/res/values/dimens.xml index 0fabc8f..a7aac53 100644 --- a/lib/src/main/res/values/dimens.xml +++ b/lib/src/main/res/values/dimens.xml @@ -1,10 +1,13 @@ 2dp - 28dp - 8dp - -8dp 1dp + + 8dp + 16dp + 8dp + 16dp + 15sp diff --git a/lib/src/main/res/values/styles.xml b/lib/src/main/res/values/styles.xml index bd42768..88a14dc 100644 --- a/lib/src/main/res/values/styles.xml +++ b/lib/src/main/res/values/styles.xml @@ -16,4 +16,10 @@ @android:color/transparent + \ No newline at end of file diff --git a/app/.gitignore b/sample/.gitignore similarity index 100% rename from app/.gitignore rename to sample/.gitignore diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 0000000..77f7676 --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 28 + + defaultConfig { + applicationId "com.yalantis.contextmenu.sample" + minSdkVersion 19 + targetSdkVersion 28 + versionCode Integer.parseInt(project.VERSION_CODE) + versionName VERSION_NAME + vectorDrawables.useSupportLibrary = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + + implementation 'com.github.yalantis:context-menu.android:1.1.4' +} \ No newline at end of file diff --git a/sample/gradle.properties b/sample/gradle.properties new file mode 100644 index 0000000..709085f --- /dev/null +++ b/sample/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=ContextMenu +POM_ARTIFACT_ID=contextmenu +POM_PACKAGING=aar \ No newline at end of file diff --git a/app/proguard-rules.pro b/sample/proguard-rules.pro similarity index 100% rename from app/proguard-rules.pro rename to sample/proguard-rules.pro diff --git a/app/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml similarity index 63% rename from app/src/main/AndroidManifest.xml rename to sample/src/main/AndroidManifest.xml index cd7c542..25164f7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,22 +1,26 @@ + xmlns:tools="http://schemas.android.com/tools" + package="com.yalantis.contextmenu"> + android:supportsRtl="true" + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> + - + - + \ No newline at end of file diff --git a/sample/src/main/java/com/yalantis/contextmenu/sample/SampleActivity.kt b/sample/src/main/java/com/yalantis/contextmenu/sample/SampleActivity.kt new file mode 100644 index 0000000..507aee3 --- /dev/null +++ b/sample/src/main/java/com/yalantis/contextmenu/sample/SampleActivity.kt @@ -0,0 +1,159 @@ +package com.yalantis.contextmenu.sample + +import android.graphics.BitmapFactory +import android.graphics.drawable.BitmapDrawable +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.view.Menu +import android.view.MenuItem +import android.widget.Toast +import com.yalantis.contextmenu.R +import com.yalantis.contextmenu.lib.ContextMenuDialogFragment +import com.yalantis.contextmenu.lib.MenuObject +import com.yalantis.contextmenu.lib.MenuParams +import kotlinx.android.synthetic.main.toolbar.* + +class SampleActivity : AppCompatActivity() { + + private lateinit var contextMenuDialogFragment: ContextMenuDialogFragment + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_sample) + + initToolbar() + initMenuFragment() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + item?.let { + when (it.itemId) { + R.id.context_menu -> { + showContextMenuDialogFragment() + } + } + } + + return super.onOptionsItemSelected(item) + } + + override fun onBackPressed() { + if (::contextMenuDialogFragment.isInitialized && contextMenuDialogFragment.isAdded) { + contextMenuDialogFragment.dismiss() + } else { + finish() + } + } + + private fun initToolbar() { + setSupportActionBar(toolbar) + + supportActionBar?.apply { + setHomeButtonEnabled(true) + setDisplayHomeAsUpEnabled(true) + setDisplayShowTitleEnabled(false) + } + + toolbar.apply { + setNavigationIcon(R.drawable.ic_arrow_back) + setNavigationOnClickListener { onBackPressed() } + } + + tvToolbarTitle.text = "Samantha" + } + + /** + * If you want to change the side you need to add 'gravity' parameter, + * by default it is MenuGravity.END. + * + * For example: + * + * MenuParams( + * actionBarSize = resources.getDimension(R.dimen.tool_bar_height).toInt(), + * menuObjects = getMenuObjects(), + * isClosableOutside = false, + * gravity = MenuGravity.START + * ) + */ + private fun initMenuFragment() { + val menuParams = MenuParams( + actionBarSize = resources.getDimension(R.dimen.tool_bar_height).toInt(), + menuObjects = getMenuObjects(), + isClosableOutside = false + ) + + contextMenuDialogFragment = ContextMenuDialogFragment.newInstance(menuParams).apply { + menuItemClickListener = { view, position -> + Toast.makeText( + this@SampleActivity, + "Clicked on position: $position", + Toast.LENGTH_SHORT + ).show() + } + menuItemLongClickListener = { view, position -> + Toast.makeText( + this@SampleActivity, + "Long clicked on position: $position", + Toast.LENGTH_SHORT + ).show() + } + } + } + + /** + * You can use any (drawable, resource, bitmap, color) as image: + * menuObject.drawable = ... + * menuObject.setResourceValue(...) + * menuObject.setBitmapValue(...) + * menuObject.setColorValue(...) + * + * You can set image ScaleType: + * menuObject.scaleType = ScaleType.FIT_XY + * + * You can use any [resource, drawable, color] as background: + * menuObject.setBgResourceValue(...) + * menuObject.setBgDrawable(...) + * menuObject.setBgColorValue(...) + * + * You can use any (color) as text color: + * menuObject.textColor = ... + * + * You can set any (color) as divider color: + * menuObject.dividerColor = ... + */ + private fun getMenuObjects() = mutableListOf().apply { + val close = MenuObject().apply { setResourceValue(R.drawable.icn_close) } + val send = MenuObject("Send message").apply { setResourceValue(R.drawable.icn_1) } + val like = MenuObject("Like profile").apply { + setBitmapValue(BitmapFactory.decodeResource(resources, R.drawable.icn_2)) + } + val addFriend = MenuObject("Add to friends").apply { + drawable = BitmapDrawable( + resources, + BitmapFactory.decodeResource(resources, R.drawable.icn_3) + ) + } + val addFavorite = MenuObject("Add to favorites").apply { + setResourceValue(R.drawable.icn_4) + } + val block = MenuObject("Block user").apply { setResourceValue(R.drawable.icn_5) } + + add(close) + add(send) + add(like) + add(addFriend) + add(addFavorite) + add(block) + } + + private fun showContextMenuDialogFragment() { + if (supportFragmentManager.findFragmentByTag(ContextMenuDialogFragment.TAG) == null) { + contextMenuDialogFragment.show(supportFragmentManager, ContextMenuDialogFragment.TAG) + } + } +} diff --git a/app/src/main/res/drawable-hdpi/btn_add.png b/sample/src/main/res/drawable-hdpi/btn_add.png similarity index 100% rename from app/src/main/res/drawable-hdpi/btn_add.png rename to sample/src/main/res/drawable-hdpi/btn_add.png diff --git a/sample/src/main/res/drawable-hdpi/ic_launcher.png b/sample/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..15f6cdf Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-hdpi/icn_1.png b/sample/src/main/res/drawable-hdpi/icn_1.png similarity index 100% rename from app/src/main/res/drawable-hdpi/icn_1.png rename to sample/src/main/res/drawable-hdpi/icn_1.png diff --git a/app/src/main/res/drawable-hdpi/icn_2.png b/sample/src/main/res/drawable-hdpi/icn_2.png similarity index 100% rename from app/src/main/res/drawable-hdpi/icn_2.png rename to sample/src/main/res/drawable-hdpi/icn_2.png diff --git a/app/src/main/res/drawable-hdpi/icn_3.png b/sample/src/main/res/drawable-hdpi/icn_3.png similarity index 100% rename from app/src/main/res/drawable-hdpi/icn_3.png rename to sample/src/main/res/drawable-hdpi/icn_3.png diff --git a/app/src/main/res/drawable-hdpi/icn_4.png b/sample/src/main/res/drawable-hdpi/icn_4.png similarity index 100% rename from app/src/main/res/drawable-hdpi/icn_4.png rename to sample/src/main/res/drawable-hdpi/icn_4.png diff --git a/app/src/main/res/drawable-hdpi/icn_5.png b/sample/src/main/res/drawable-hdpi/icn_5.png similarity index 100% rename from app/src/main/res/drawable-hdpi/icn_5.png rename to sample/src/main/res/drawable-hdpi/icn_5.png diff --git a/app/src/main/res/drawable-hdpi/icn_close.png b/sample/src/main/res/drawable-hdpi/icn_close.png similarity index 100% rename from app/src/main/res/drawable-hdpi/icn_close.png rename to sample/src/main/res/drawable-hdpi/icn_close.png diff --git a/app/src/main/res/drawable-hdpi/photo.png b/sample/src/main/res/drawable-hdpi/photo.png similarity index 100% rename from app/src/main/res/drawable-hdpi/photo.png rename to sample/src/main/res/drawable-hdpi/photo.png diff --git a/app/src/main/res/drawable-mdpi/btn_add.png b/sample/src/main/res/drawable-mdpi/btn_add.png similarity index 100% rename from app/src/main/res/drawable-mdpi/btn_add.png rename to sample/src/main/res/drawable-mdpi/btn_add.png diff --git a/sample/src/main/res/drawable-mdpi/ic_launcher.png b/sample/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..a17ef93 Binary files /dev/null and b/sample/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-mdpi/icn_1.png b/sample/src/main/res/drawable-mdpi/icn_1.png similarity index 100% rename from app/src/main/res/drawable-mdpi/icn_1.png rename to sample/src/main/res/drawable-mdpi/icn_1.png diff --git a/app/src/main/res/drawable-mdpi/icn_2.png b/sample/src/main/res/drawable-mdpi/icn_2.png similarity index 100% rename from app/src/main/res/drawable-mdpi/icn_2.png rename to sample/src/main/res/drawable-mdpi/icn_2.png diff --git a/app/src/main/res/drawable-mdpi/icn_3.png b/sample/src/main/res/drawable-mdpi/icn_3.png similarity index 100% rename from app/src/main/res/drawable-mdpi/icn_3.png rename to sample/src/main/res/drawable-mdpi/icn_3.png diff --git a/app/src/main/res/drawable-mdpi/icn_4.png b/sample/src/main/res/drawable-mdpi/icn_4.png similarity index 100% rename from app/src/main/res/drawable-mdpi/icn_4.png rename to sample/src/main/res/drawable-mdpi/icn_4.png diff --git a/app/src/main/res/drawable-mdpi/icn_5.png b/sample/src/main/res/drawable-mdpi/icn_5.png similarity index 100% rename from app/src/main/res/drawable-mdpi/icn_5.png rename to sample/src/main/res/drawable-mdpi/icn_5.png diff --git a/app/src/main/res/drawable-mdpi/icn_close.png b/sample/src/main/res/drawable-mdpi/icn_close.png similarity index 100% rename from app/src/main/res/drawable-mdpi/icn_close.png rename to sample/src/main/res/drawable-mdpi/icn_close.png diff --git a/app/src/main/res/drawable-mdpi/photo.png b/sample/src/main/res/drawable-mdpi/photo.png similarity index 100% rename from app/src/main/res/drawable-mdpi/photo.png rename to sample/src/main/res/drawable-mdpi/photo.png diff --git a/app/src/main/res/drawable-xhdpi/btn_add.png b/sample/src/main/res/drawable-xhdpi/btn_add.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/btn_add.png rename to sample/src/main/res/drawable-xhdpi/btn_add.png diff --git a/sample/src/main/res/drawable-xhdpi/ic_launcher.png b/sample/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..7d007fe Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xhdpi/icn_1.png b/sample/src/main/res/drawable-xhdpi/icn_1.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/icn_1.png rename to sample/src/main/res/drawable-xhdpi/icn_1.png diff --git a/app/src/main/res/drawable-xhdpi/icn_2.png b/sample/src/main/res/drawable-xhdpi/icn_2.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/icn_2.png rename to sample/src/main/res/drawable-xhdpi/icn_2.png diff --git a/app/src/main/res/drawable-xhdpi/icn_3.png b/sample/src/main/res/drawable-xhdpi/icn_3.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/icn_3.png rename to sample/src/main/res/drawable-xhdpi/icn_3.png diff --git a/app/src/main/res/drawable-xhdpi/icn_4.png b/sample/src/main/res/drawable-xhdpi/icn_4.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/icn_4.png rename to sample/src/main/res/drawable-xhdpi/icn_4.png diff --git a/app/src/main/res/drawable-xhdpi/icn_5.png b/sample/src/main/res/drawable-xhdpi/icn_5.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/icn_5.png rename to sample/src/main/res/drawable-xhdpi/icn_5.png diff --git a/app/src/main/res/drawable-xhdpi/icn_close.png b/sample/src/main/res/drawable-xhdpi/icn_close.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/icn_close.png rename to sample/src/main/res/drawable-xhdpi/icn_close.png diff --git a/app/src/main/res/drawable-xhdpi/photo.png b/sample/src/main/res/drawable-xhdpi/photo.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/photo.png rename to sample/src/main/res/drawable-xhdpi/photo.png diff --git a/app/src/main/res/drawable-xxhdpi/btn_add.png b/sample/src/main/res/drawable-xxhdpi/btn_add.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/btn_add.png rename to sample/src/main/res/drawable-xxhdpi/btn_add.png diff --git a/sample/src/main/res/drawable-xxhdpi/ic_launcher.png b/sample/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..0e39e6b Binary files /dev/null and b/sample/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icn_1.png b/sample/src/main/res/drawable-xxhdpi/icn_1.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/icn_1.png rename to sample/src/main/res/drawable-xxhdpi/icn_1.png diff --git a/app/src/main/res/drawable-xxhdpi/icn_2.png b/sample/src/main/res/drawable-xxhdpi/icn_2.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/icn_2.png rename to sample/src/main/res/drawable-xxhdpi/icn_2.png diff --git a/app/src/main/res/drawable-xxhdpi/icn_3.png b/sample/src/main/res/drawable-xxhdpi/icn_3.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/icn_3.png rename to sample/src/main/res/drawable-xxhdpi/icn_3.png diff --git a/app/src/main/res/drawable-xxhdpi/icn_4.png b/sample/src/main/res/drawable-xxhdpi/icn_4.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/icn_4.png rename to sample/src/main/res/drawable-xxhdpi/icn_4.png diff --git a/app/src/main/res/drawable-xxhdpi/icn_5.png b/sample/src/main/res/drawable-xxhdpi/icn_5.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/icn_5.png rename to sample/src/main/res/drawable-xxhdpi/icn_5.png diff --git a/app/src/main/res/drawable-xxhdpi/icn_close.png b/sample/src/main/res/drawable-xxhdpi/icn_close.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/icn_close.png rename to sample/src/main/res/drawable-xxhdpi/icn_close.png diff --git a/app/src/main/res/drawable-xxhdpi/photo.png b/sample/src/main/res/drawable-xxhdpi/photo.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/photo.png rename to sample/src/main/res/drawable-xxhdpi/photo.png diff --git a/app/src/main/res/drawable-xxxhdpi/btn_add.png b/sample/src/main/res/drawable-xxxhdpi/btn_add.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/btn_add.png rename to sample/src/main/res/drawable-xxxhdpi/btn_add.png diff --git a/sample/src/main/res/drawable-xxxhdpi/ic_launcher.png b/sample/src/main/res/drawable-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..6bb59a5 Binary files /dev/null and b/sample/src/main/res/drawable-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/icn_1.png b/sample/src/main/res/drawable-xxxhdpi/icn_1.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/icn_1.png rename to sample/src/main/res/drawable-xxxhdpi/icn_1.png diff --git a/app/src/main/res/drawable-xxxhdpi/icn_2.png b/sample/src/main/res/drawable-xxxhdpi/icn_2.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/icn_2.png rename to sample/src/main/res/drawable-xxxhdpi/icn_2.png diff --git a/app/src/main/res/drawable-xxxhdpi/icn_3.png b/sample/src/main/res/drawable-xxxhdpi/icn_3.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/icn_3.png rename to sample/src/main/res/drawable-xxxhdpi/icn_3.png diff --git a/app/src/main/res/drawable-xxxhdpi/icn_4.png b/sample/src/main/res/drawable-xxxhdpi/icn_4.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/icn_4.png rename to sample/src/main/res/drawable-xxxhdpi/icn_4.png diff --git a/app/src/main/res/drawable-xxxhdpi/icn_5.png b/sample/src/main/res/drawable-xxxhdpi/icn_5.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/icn_5.png rename to sample/src/main/res/drawable-xxxhdpi/icn_5.png diff --git a/app/src/main/res/drawable-xxxhdpi/icn_close.png b/sample/src/main/res/drawable-xxxhdpi/icn_close.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/icn_close.png rename to sample/src/main/res/drawable-xxxhdpi/icn_close.png diff --git a/app/src/main/res/drawable-xxxhdpi/photo.png b/sample/src/main/res/drawable-xxxhdpi/photo.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/photo.png rename to sample/src/main/res/drawable-xxxhdpi/photo.png diff --git a/sample/src/main/res/drawable/ic_arrow_back.xml b/sample/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 0000000..bdec838 --- /dev/null +++ b/sample/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,5 @@ + + + diff --git a/sample/src/main/res/layout/activity_sample.xml b/sample/src/main/res/layout/activity_sample.xml new file mode 100644 index 0000000..1e4d827 --- /dev/null +++ b/sample/src/main/res/layout/activity_sample.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/scrollable_content.xml b/sample/src/main/res/layout/scrollable_content.xml new file mode 100644 index 0000000..b7505cf --- /dev/null +++ b/sample/src/main/res/layout/scrollable_content.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/toolbar.xml b/sample/src/main/res/layout/toolbar.xml similarity index 70% rename from app/src/main/res/layout/toolbar.xml rename to sample/src/main/res/layout/toolbar.xml index b6b12c1..34893a8 100644 --- a/app/src/main/res/layout/toolbar.xml +++ b/sample/src/main/res/layout/toolbar.xml @@ -2,19 +2,19 @@ + app:theme="@style/ThemeOverlay.AppCompat.ActionBar"> + android:textSize="@dimen/tool_bar_text_size" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/sample/src/main/res/menu/menu_main.xml similarity index 100% rename from app/src/main/res/menu/menu_main.xml rename to sample/src/main/res/menu/menu_main.xml diff --git a/app/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml similarity index 100% rename from app/src/main/res/values/colors.xml rename to sample/src/main/res/values/colors.xml diff --git a/app/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml similarity index 100% rename from app/src/main/res/values/dimens.xml rename to sample/src/main/res/values/dimens.xml diff --git a/app/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml similarity index 100% rename from app/src/main/res/values/strings.xml rename to sample/src/main/res/values/strings.xml diff --git a/app/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml similarity index 100% rename from app/src/main/res/values/styles.xml rename to sample/src/main/res/values/styles.xml diff --git a/screenshot.jpg b/screenshot.jpg new file mode 100644 index 0000000..d7a5722 Binary files /dev/null and b/screenshot.jpg differ diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..9c38812 Binary files /dev/null and b/screenshot.png differ diff --git a/settings.gradle b/settings.gradle index 3cbe249..035bb58 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':lib' +include ':sample', ':lib'