├── .gitignore ├── LICENSE ├── README.md ├── StarWars.iml ├── build.gradle ├── demo ├── .gitignore ├── build.gradle ├── demo.iml ├── fabric.properties ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yalantis │ │ └── starwarsdemo │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yalantis │ │ │ └── starwarsdemo │ │ │ ├── App.java │ │ │ ├── adapter │ │ │ └── ProfileAdapter.java │ │ │ ├── interfaces │ │ │ ├── DemoActivityInterface.java │ │ │ ├── GreetingFragmentInterface.java │ │ │ ├── ProfileAdapterListener.java │ │ │ └── TilesRendererInterface.java │ │ │ ├── model │ │ │ └── User.java │ │ │ ├── particlesys │ │ │ ├── ParticleSystem.java │ │ │ ├── ParticleSystemRenderer.java │ │ │ └── ParticlesGenerator.java │ │ │ ├── util │ │ │ └── gl │ │ │ │ ├── Const.java │ │ │ │ └── math │ │ │ │ └── MathHelper.java │ │ │ ├── view │ │ │ ├── BrightSideFragment.java │ │ │ ├── DarkSideFragment.java │ │ │ ├── DemoActivity.java │ │ │ ├── GreetingsFragment.java │ │ │ └── SideFragment.java │ │ │ └── widget │ │ │ ├── BackgroundDrawableSwitchCompat.java │ │ │ ├── CenterTopImageView.java │ │ │ └── ClipRevealFrame.java │ └── res │ │ ├── anim │ │ ├── slide_downward.xml │ │ └── slide_upward.xml │ │ ├── drawable-hdpi │ │ ├── anakin.png │ │ ├── darth.png │ │ ├── ic_close.png │ │ ├── star_wars_logo.png │ │ └── track_off.png │ │ ├── drawable-mdpi │ │ └── anakin.png │ │ ├── drawable-v21 │ │ └── button_setup_profile.xml │ │ ├── drawable-xhdpi │ │ ├── anakin.png │ │ ├── darth.png │ │ ├── ic_close.png │ │ ├── star_wars_logo.png │ │ └── track_off.png │ │ ├── drawable-xxhdpi │ │ ├── darth.png │ │ ├── ic_close.png │ │ ├── ic_menu.png │ │ ├── star_wars_logo.png │ │ └── track_off.png │ │ ├── drawable │ │ ├── btn_shape.xml │ │ ├── button_setup_profile.xml │ │ ├── leia.jpg │ │ ├── star_wars_logo.png │ │ ├── track_off.png │ │ └── track_on.png │ │ ├── layout │ │ ├── activity_demo.xml │ │ ├── fragment_greetings.xml │ │ ├── fragment_side.xml │ │ ├── fragment_star_wars.xml │ │ ├── item_profile_gender.xml │ │ ├── item_profile_other.xml │ │ └── item_profile_side.xml │ │ ├── menu │ │ └── menu_star_wars.xml │ │ ├── mipmap-nodpi │ │ └── ic_launcher.png │ │ ├── raw │ │ ├── star_frag.glsl │ │ └── star_vert.glsl │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── yalantis │ └── starwars │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── library.iml ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── yalantis │ │ └── starwars │ │ ├── Const.java │ │ ├── TilesFrameLayout.java │ │ ├── interfaces │ │ ├── Renderable.java │ │ ├── ShaderType.java │ │ └── TilesFrameLayoutListener.java │ │ ├── render │ │ ├── Buffers.java │ │ ├── GenerateVerticesData.java │ │ ├── StarWarsRenderer.java │ │ └── StarWarsTiles.java │ │ ├── utils │ │ └── gl │ │ │ ├── RawResourceReader.java │ │ │ ├── ShaderHelper.java │ │ │ └── TextureHelper.java │ │ └── widget │ │ └── StarWarsTilesGLSurfaceView.java │ └── res │ ├── raw │ ├── tiles_frag.glsl │ └── tiles_vert.glsl │ └── values │ ├── attrs_sw_tiles.xml │ └── strings.xml ├── settings.gradle └── star_wars-concept.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | /.ignored 6 | .DS_Store 7 | /build 8 | /captures 9 | /logs 10 | /.idea 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2015 Yalantis, https://yalantis.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StarWars.Android 2 | 3 | [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)]() 4 | [![Maven](https://img.shields.io/maven-central/v/com.yalantis/starwarstiles.svg)]() 5 | [![](https://jitpack.io/v/yalantis/starwars.android.svg)](https://jitpack.io/#yalantis/starwars.android) 6 | [![Yalantis](https://raw.githubusercontent.com/Yalantis/PullToRefresh/develop/PullToRefreshDemo/Resources/badge_dark.png)](https://yalantis.com/?utm_source=github) 7 | 8 | This component implements transition animation to crumble view into tiny pieces. 9 | 10 | 11 | 12 | 13 |
Check this project on dribbble. 14 | 15 | Also, read how it was done in [our blog](https://yalantis.com/blog/star-wars-the-force-awakens-or-how-to-crumble-view-into-tiny-pieces-on-android) 16 | 17 | ##Requirements 18 | - Android SDK 15+ 19 | - OpenGL ES 2.0+ 20 | 21 | ##Usage 22 | 23 | Add to your module's build.gradle: 24 | ```Groovy 25 | dependencies { 26 | //... 27 | compile 'com.yalantis:starwarstiles:0.1.1' 28 | } 29 | ``` 30 | 31 | Wrap your fragment or activity main view in TilesFrameLayout: 32 | ```xml 33 | 39 | 40 | 41 | 42 | 43 | ``` 44 | 45 | 46 | Adjust animation with these parameters: 47 | - ```app:sw_animationDuration``` – duration in milliseconds 48 | - ```app:sw_numberOfTilesX``` – the number of square tiles the plane is tessellated into broadwise 49 | 50 | ```java 51 | mTilesFrameLayout = (TilesFrameLayout) findViewById(R.id.tiles_frame); 52 | mTilesFrameLayout.setOnAnimationFinishedListener(this); 53 | ``` 54 | In your activity or fragment’s onPause() and onResume() it’s important to call the corresponding methods: 55 | ```java 56 | @Override 57 | public void onResume() { 58 | super.onResume(); 59 | mTilesFrameLayout.onResume(); 60 | } 61 | 62 | @Override 63 | public void onPause() { 64 | super.onPause(); 65 | mTilesFrameLayout.onPause(); 66 | } 67 | ``` 68 | To start the animation simply call: 69 | ```java 70 | mTilesFrameLayout.startAnimation(); 71 | ``` 72 | Your callback will be called when the animation ends: 73 | ```java 74 | @Override 75 | public void onAnimationFinished() { 76 | // Hide or remove your view/fragment/activity here 77 | } 78 | ``` 79 | 80 | Have fun! :) 81 | 82 | #### Let us know! 83 | 84 | 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. 85 | 86 | P.S. We’re going to publish more awesomeness wrapped in code and a tutorial on how to make UI for iOS (Android) better than better. Stay tuned! 87 | 88 | ## License 89 | 90 | The MIT License (MIT) 91 | 92 | Copyright © 2017 Yalantis, https://yalantis.com 93 | 94 | Permission is hereby granted, free of charge, to any person obtaining a copy 95 | of this software and associated documentation files (the "Software"), to deal 96 | in the Software without restriction, including without limitation the rights 97 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 98 | copies of the Software, and to permit persons to whom the Software is 99 | furnished to do so, subject to the following conditions: 100 | 101 | The above copyright notice and this permission notice shall be included in 102 | all copies or substantial portions of the Software. 103 | 104 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 105 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 106 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 107 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 108 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 109 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 110 | THE SOFTWARE. 111 | 112 | -------------------------------------------------------------------------------- /StarWars.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | maven { url 'https://maven.fabric.io/public' } 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:2.1.0-alpha3' 10 | classpath 'io.fabric.tools:gradle:1.21.0' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | jcenter() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url 'https://maven.fabric.io/public' } 4 | } 5 | 6 | dependencies { 7 | classpath 'io.fabric.tools:gradle:1.21.0' 8 | } 9 | } 10 | apply plugin: 'com.android.application' 11 | 12 | repositories { 13 | maven { url 'https://maven.fabric.io/public' } 14 | } 15 | 16 | apply plugin: 'io.fabric' 17 | 18 | 19 | android { 20 | compileSdkVersion 23 21 | buildToolsVersion '23.0.2' 22 | 23 | defaultConfig { 24 | applicationId "com.yalantis.starwarsdemo" 25 | minSdkVersion 15 26 | targetSdkVersion 23 27 | versionCode 1 28 | versionName "0.1.0" 29 | } 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | dataBinding { 37 | enabled = false 38 | } 39 | } 40 | 41 | dependencies { 42 | compile fileTree(dir: 'libs', include: ['*.jar']) 43 | testCompile 'junit:junit:4.12' 44 | compile 'com.android.support:appcompat-v7:23.1.1' 45 | compile 'com.android.support:design:23.1.1' 46 | compile 'com.jakewharton.timber:timber:4.1.0' 47 | compile 'com.jakewharton:butterknife:7.0.1' 48 | compile 'com.android.support:recyclerview-v7:23.1.1' 49 | compile 'com.android.support:percent:23.1.1' 50 | compile project(':library') 51 | compile('com.crashlytics.sdk.android:crashlytics:2.5.3@aar') { 52 | transitive = true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /demo/demo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /demo/fabric.properties: -------------------------------------------------------------------------------- 1 | apiKey=fce5ef09bbc74ca549bb0716a8f911f0b913e4be 2 | apiSecret=e4ebb3d3fd9e249e06ef2a0269fe21a298a97701ba57956187c912aba956991c 3 | -------------------------------------------------------------------------------- /demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/ice/dev/android-sdk-linux/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /demo/src/androidTest/java/com/yalantis/starwarsdemo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/App.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.crashlytics.android.Crashlytics; 7 | import io.fabric.sdk.android.Fabric; 8 | import timber.log.Timber; 9 | 10 | /** 11 | * Created by Artem Kholodnyi on 11/2/15. 12 | */ 13 | public class App extends Application{ 14 | private static App app; 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | Fabric.with(this, new Crashlytics()); 20 | app = this; 21 | 22 | Timber.plant(new Timber.DebugTree()); 23 | } 24 | 25 | public static Context getAppContext() { 26 | return app; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/adapter/ProfileAdapter.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.LayoutRes; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.CompoundButton; 11 | import android.widget.TextView; 12 | 13 | import com.yalantis.starwarsdemo.R; 14 | import com.yalantis.starwarsdemo.interfaces.ProfileAdapterListener; 15 | import com.yalantis.starwarsdemo.model.User; 16 | import com.yalantis.starwarsdemo.widget.BackgroundDrawableSwitchCompat; 17 | 18 | import butterknife.Bind; 19 | import butterknife.ButterKnife; 20 | 21 | /** 22 | * Created by Artem Kholodnyi on 11/17/15. 23 | */ 24 | public class ProfileAdapter extends RecyclerView.Adapter { 25 | private static final int VIEW_TYPE_SIDE = 0; 26 | private static final int VIEW_TYPE_TEXT_FIELD = 1; 27 | private static final int VIEW_GENDER_FIELD = 2; 28 | private final ProfileAdapterListener mListener; 29 | private User mUser; 30 | 31 | private final String mFullNameLabel; 32 | private final String mHomeWorldLabel; 33 | private final String mBirthdayLabel; 34 | private final String mGenderLabel; 35 | 36 | public ProfileAdapter(Context context, User user, ProfileAdapterListener listener) { 37 | mUser = user; 38 | mListener = listener; 39 | 40 | mFullNameLabel = context.getString(R.string.label_full_name); 41 | mHomeWorldLabel = context.getString(R.string.label_homeworld); 42 | mBirthdayLabel = context.getString(R.string.label_birthday); 43 | mGenderLabel = context.getString(R.string.label_gender); 44 | } 45 | 46 | @Override 47 | public int getItemViewType(int position) { 48 | switch (position) { 49 | case 0: 50 | return VIEW_TYPE_SIDE; 51 | case 1: 52 | case 2: 53 | case 3: 54 | return VIEW_TYPE_TEXT_FIELD; 55 | case 4: 56 | return VIEW_GENDER_FIELD; 57 | default: 58 | throw new IllegalStateException(); 59 | } 60 | } 61 | 62 | @Override 63 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 64 | @LayoutRes int layoutRes = getLayoutId(viewType); 65 | 66 | View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false); 67 | return new ViewHolder(itemView); 68 | } 69 | 70 | @LayoutRes 71 | private int getLayoutId(int viewType) { 72 | switch (viewType) { 73 | case VIEW_TYPE_SIDE: 74 | return R.layout.item_profile_side; 75 | case VIEW_TYPE_TEXT_FIELD: 76 | return R.layout.item_profile_other; 77 | case VIEW_GENDER_FIELD: 78 | return R.layout.item_profile_gender; 79 | default: 80 | throw new IllegalStateException(); 81 | } 82 | } 83 | 84 | @SuppressWarnings("ConstantConditions") 85 | @Override 86 | public void onBindViewHolder(final ViewHolder holder, int position) { 87 | switch (position) { 88 | case 0: 89 | // holder.getBinding().setVariable(BR.callback, mListener); 90 | // holder.getBinding().setVariable(BR.user, mUser); 91 | holder.mySwitch.setCheckedImmediate(mUser.isDarkSide()); 92 | holder.mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 93 | @Override 94 | public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { 95 | mListener.onSideSwitch(holder.mySwitch); 96 | } 97 | }); 98 | holder.label.setText(mUser.getSideText()); 99 | break; 100 | case 1: 101 | holder.label.setText(mFullNameLabel); 102 | holder.value.setText(mUser.getFullName()); 103 | break; 104 | case 2: 105 | holder.label.setText(mHomeWorldLabel); 106 | holder.value.setText(mUser.getHomeworld()); 107 | break; 108 | case 3: 109 | holder.label.setText(mBirthdayLabel); 110 | holder.value.setText(mUser.getBirthday()); 111 | break; 112 | case 4: 113 | holder.label.setText(mGenderLabel); 114 | break; 115 | default: 116 | throw new IllegalStateException("unknown"); 117 | } 118 | } 119 | 120 | @Override 121 | public int getItemCount() { 122 | return 5; 123 | } 124 | 125 | public class ViewHolder extends RecyclerView.ViewHolder { 126 | @Nullable 127 | @Bind(R.id.side_switch) 128 | BackgroundDrawableSwitchCompat mySwitch; 129 | 130 | @Nullable 131 | @Bind(R.id.tv_label) 132 | TextView label; 133 | 134 | @Nullable 135 | @Bind(R.id.tv_value) 136 | TextView value; 137 | 138 | public ViewHolder(View itemView) { 139 | super(itemView); 140 | ButterKnife.bind(this, itemView); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/interfaces/DemoActivityInterface.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.interfaces; 2 | 3 | /** 4 | * Created by Artem Kholodnyi on 11/19/15. 5 | */ 6 | public interface DemoActivityInterface { 7 | void goToSide(int cx, int cy, boolean appBarExpanded, String side); 8 | void removeAllFragmentExcept(String tag); 9 | } 10 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/interfaces/GreetingFragmentInterface.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.interfaces; 2 | 3 | /** 4 | * Created by Artem Kholodnyi on 11/17/15. 5 | */ 6 | public interface GreetingFragmentInterface { 7 | void onSetupProfileClick(); 8 | } 9 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/interfaces/ProfileAdapterListener.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.interfaces; 2 | 3 | import android.support.v7.widget.SwitchCompat; 4 | 5 | /** 6 | * Created by Artem Kholodnyi on 11/17/15. 7 | */ 8 | public interface ProfileAdapterListener { 9 | void onSideSwitch(SwitchCompat view); 10 | } 11 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/interfaces/TilesRendererInterface.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.interfaces; 2 | 3 | /** 4 | * Created by Artem Kholodnyi on 11/26/15. 5 | */ 6 | public interface TilesRendererInterface { 7 | void onTilesFinished(); 8 | } 9 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/model/User.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.model; 2 | 3 | import android.support.annotation.DrawableRes; 4 | import android.support.annotation.StringRes; 5 | 6 | import com.yalantis.starwarsdemo.R; 7 | 8 | /** 9 | * Created by Artem Kholodnyi on 11/19/15. 10 | */ 11 | public class User { 12 | private final String mFullName; 13 | private final boolean mDarkSide; 14 | private final String mHomeworld; 15 | private final String mBirthday; 16 | 17 | public User(boolean darkSide, String fullName, String homeworld, String birthday) { 18 | mDarkSide = darkSide; 19 | mFullName = fullName; 20 | mHomeworld = homeworld; 21 | mBirthday = birthday; 22 | } 23 | 24 | public @DrawableRes int getPhotoRes() { 25 | return mDarkSide ? R.drawable.darth : R.drawable.anakin; 26 | } 27 | 28 | public boolean isDarkSide() { 29 | return mDarkSide; 30 | } 31 | 32 | public String getFullName() { 33 | return mFullName; 34 | } 35 | 36 | public String getHomeworld() { 37 | return mHomeworld; 38 | } 39 | 40 | public String getBirthday() { 41 | return mBirthday; 42 | } 43 | 44 | public @StringRes int getSideText() { 45 | return isDarkSide() ? R.string.dark_side_label : R.string.light_side_label; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/particlesys/ParticleSystem.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.particlesys; 2 | 3 | import android.opengl.GLES20; 4 | 5 | import com.yalantis.starwars.interfaces.Renderable; 6 | import com.yalantis.starwarsdemo.util.gl.Const; 7 | 8 | import java.nio.ByteBuffer; 9 | import java.nio.ByteOrder; 10 | import java.nio.FloatBuffer; 11 | import java.nio.ShortBuffer; 12 | 13 | import timber.log.Timber; 14 | 15 | import static android.opengl.GLES20.GL_ARRAY_BUFFER; 16 | import static android.opengl.GLES20.GL_STATIC_DRAW; 17 | import static android.opengl.GLES20.glBindBuffer; 18 | import static android.opengl.GLES20.glBufferData; 19 | import static android.opengl.GLES20.glGenBuffers; 20 | 21 | /** 22 | * Created by Artem Kholodnyi on 11/12/15. 23 | */ 24 | public class ParticleSystem implements Renderable { 25 | private final ParticleSystemRenderer mRenderer; 26 | public static final int PARTICLE_COUNT = 1_000; 27 | private int mBufferId; 28 | 29 | public static final int POS_DATA_SIZE = 3; 30 | public static final int TEXTURE_COORDS_DATA_SIZE = 2; 31 | public static final int MISC_DATA_SIZE = 3; 32 | 33 | 34 | public ParticleSystem(ParticleSystemRenderer renderer, FloatBuffer vertexBuffer) { 35 | long startTime = System.currentTimeMillis(); 36 | 37 | mRenderer = renderer; 38 | 39 | Timber.d("generated in %d ms", System.currentTimeMillis() - startTime); 40 | 41 | // Copy buffer into OpenGL's memory. After, we don't need to keep the client-side buffers around. 42 | final int buffers[] = new int[1]; 43 | glGenBuffers(1, buffers, 0); 44 | 45 | glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); 46 | glBufferData(GL_ARRAY_BUFFER, vertexBuffer.capacity() * Const.BYTES_PER_FLOAT, vertexBuffer, GL_STATIC_DRAW); 47 | 48 | glBindBuffer(GL_ARRAY_BUFFER, 0); 49 | 50 | mBufferId = buffers[0]; 51 | 52 | vertexBuffer.limit(0); 53 | Timber.d("done in %d ms", System.currentTimeMillis() - startTime); 54 | } 55 | 56 | 57 | // use to make native order buffers 58 | private ShortBuffer makeShortBuffer(short[] arr) { 59 | ByteBuffer bb = ByteBuffer.allocateDirect(arr.length*4); 60 | bb.order(ByteOrder.nativeOrder()); 61 | ShortBuffer ib = bb.asShortBuffer(); 62 | ib.put(arr); 63 | ib.position(0); 64 | return ib; 65 | } 66 | 67 | @Override 68 | public void render() { 69 | final int stride = Const.BYTES_PER_FLOAT 70 | * (POS_DATA_SIZE + TEXTURE_COORDS_DATA_SIZE + MISC_DATA_SIZE); 71 | 72 | // a_Position 73 | glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBufferId); 74 | GLES20.glEnableVertexAttribArray(mRenderer.positionHandle); 75 | GLES20.glVertexAttribPointer(mRenderer.positionHandle, 76 | POS_DATA_SIZE, 77 | GLES20.GL_FLOAT, 78 | false, 79 | stride, 80 | 0 81 | ); 82 | 83 | // a_TexCoordinate 84 | glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBufferId); 85 | GLES20.glEnableVertexAttribArray(mRenderer.textureCoordinateHandle); 86 | GLES20.glVertexAttribPointer(mRenderer.textureCoordinateHandle, 87 | TEXTURE_COORDS_DATA_SIZE, 88 | GLES20.GL_FLOAT, 89 | false, 90 | stride, 91 | Const.BYTES_PER_FLOAT * (POS_DATA_SIZE) 92 | ); 93 | 94 | // a_Misc 95 | glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBufferId); 96 | GLES20.glEnableVertexAttribArray(mRenderer.miscHandle); 97 | GLES20.glVertexAttribPointer(mRenderer.miscHandle, 98 | MISC_DATA_SIZE, 99 | GLES20.GL_FLOAT, 100 | false, 101 | stride, 102 | Const.BYTES_PER_FLOAT * (POS_DATA_SIZE + TEXTURE_COORDS_DATA_SIZE) 103 | ); 104 | 105 | // Clear the currently bound buffer (so future OpenGL calls do not use this buffer). 106 | glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 107 | 108 | // Draw tiles 109 | GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, PARTICLE_COUNT * 6); 110 | } 111 | 112 | @Override 113 | public void release() { 114 | final int[] buffersToDelete = new int[] { mBufferId }; 115 | GLES20.glDeleteBuffers(buffersToDelete.length, buffersToDelete, 0); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/particlesys/ParticleSystemRenderer.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.particlesys; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.graphics.Bitmap; 5 | import android.opengl.GLES20; 6 | import android.opengl.GLSurfaceView; 7 | import android.opengl.Matrix; 8 | 9 | import com.yalantis.starwars.utils.gl.RawResourceReader; 10 | import com.yalantis.starwars.utils.gl.ShaderHelper; 11 | import com.yalantis.starwarsdemo.App; 12 | import com.yalantis.starwarsdemo.R; 13 | 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Executors; 16 | 17 | import javax.microedition.khronos.egl.EGLConfig; 18 | import javax.microedition.khronos.opengles.GL10; 19 | 20 | import timber.log.Timber; 21 | 22 | /** 23 | * Created by Artem Kholodnyi on 11/12/15. 24 | */ 25 | public class ParticleSystemRenderer implements GLSurfaceView.Renderer { 26 | public float ratio; 27 | public int mvpMatrixHandle; 28 | public int mvMatrixHandle = -1; 29 | public int positionHandle; 30 | public int normalHandle; 31 | public int textureCoordinateHandle; 32 | public int programHandle; 33 | public int miscHandle; 34 | public int sizeX = 35; 35 | public int sizeY = 70; 36 | public float mTime; 37 | private GLSurfaceView mGlSurfaceView; 38 | /** 39 | * Store the model matrix. This matrix is used to move models from object space (where each model can be thought 40 | * of being located at the center of the universe) to world space. 41 | */ 42 | private float[] mModelMatrix = new float[16]; 43 | /** 44 | * Store the view matrix. This can be thought of as our camera. This matrix transforms world space to eye space; 45 | * it positions things relative to our eye. 46 | */ 47 | private float[] mViewMatrix = new float[16]; 48 | /** Store the projection matrix. This is used to project the scene onto a 2D viewport. */ 49 | private float[] mProjectionMatrix = new float[16]; 50 | /** Allocate storage for the final combined matrix. This will be passed into the shader program. */ 51 | private float[] mMVPMatrix = new float[16]; 52 | private float[] mTemporaryMatrix = new float[16]; 53 | private int timeHandle; 54 | private long mStartTime; 55 | private int frames; 56 | private long startTime; 57 | private boolean mStart; 58 | private long timePassed; 59 | private float dt; 60 | private long t_current; 61 | private long t_prev; 62 | private float dt_prev = 1; 63 | private ValueAnimator animator; 64 | private Bitmap mBitmap; 65 | private ParticleSystem mParticleSystem; 66 | private int resolutionHandle; 67 | private int mWidth; 68 | private int mHeight; 69 | private int timesRepeated; 70 | private float delta; 71 | private ExecutorService mExecutor = Executors.newSingleThreadExecutor(); 72 | 73 | 74 | public ParticleSystemRenderer(GLSurfaceView glSurfaceView) { 75 | mGlSurfaceView = glSurfaceView; 76 | } 77 | 78 | @Override 79 | public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { 80 | GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 81 | 82 | // Use culling to remove back faces. 83 | GLES20.glEnable(GLES20.GL_CULL_FACE); 84 | GLES20.glFrontFace(GLES20.GL_CW); 85 | 86 | // Enable depth testing 87 | GLES20.glEnable(GLES20.GL_DEPTH_TEST); 88 | 89 | // Position the eye in front of the origin. 90 | final float eyeX = 0.0f; 91 | final float eyeY = 0.0f; 92 | final float eyeZ = 0.0f; 93 | 94 | // We are looking toward the distance 95 | final float lookX = 0.0f; 96 | final float lookY = 0.0f; 97 | final float lookZ = 1.0f; 98 | 99 | // Set our up vector. This is where our head would be pointing were we holding the camera. 100 | final float upX = 0.0f; 101 | final float upY = 1.0f; 102 | final float upZ = 0.0f; 103 | 104 | Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ); 105 | 106 | final String vertexShader = RawResourceReader.readTextFileFromRawResource(App.getAppContext(), R.raw.star_vert); 107 | final String fragmentShader = RawResourceReader.readTextFileFromRawResource(App.getAppContext(), R.raw.star_frag); 108 | 109 | final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertexShader); 110 | final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader); 111 | 112 | programHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle, 113 | new String[]{"a_Position", "a_TexCoordinate", "a_TileXY"}); 114 | } 115 | 116 | /** 117 | * 118 | */ 119 | @Override 120 | public void onSurfaceChanged(GL10 unused, int width, int height) { 121 | // Set the OpenGL viewport to the same size as the surface. 122 | GLES20.glViewport(0, 0, width, height); 123 | 124 | mWidth = width; 125 | mHeight = height; 126 | 127 | // Create a new perspective projection matrix. The height will stay the same 128 | // while the width will vary as per aspect ratio. 129 | final float ratio = (float) width / height; 130 | 131 | final float left = -ratio; 132 | @SuppressWarnings("UnnecessaryLocalVariable") 133 | final float right = ratio; 134 | final float bottom = -1.0f; 135 | final float top = 1.0f; 136 | final float near = 1.0f; 137 | final float far = 10.0f; 138 | 139 | this.ratio = ratio; 140 | 141 | Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far); 142 | 143 | mStartTime = System.currentTimeMillis(); 144 | 145 | mExecutor.execute(new ParticlesGenerator(this)); 146 | } 147 | 148 | @Override 149 | public void onDrawFrame(GL10 gl10) { 150 | logFrame(); 151 | drawGl(); 152 | 153 | if (mParticleSystem != null) { 154 | mParticleSystem.render(); 155 | } 156 | } 157 | 158 | private void drawGl() { 159 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); 160 | 161 | GLES20.glUseProgram(programHandle); 162 | 163 | // Set program handles 164 | mvpMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVPMatrix"); 165 | mvMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVMatrix"); 166 | timeHandle = GLES20.glGetUniformLocation(programHandle, "u_Time"); 167 | resolutionHandle = GLES20.glGetUniformLocation(programHandle, "u_Resolution"); 168 | 169 | positionHandle = GLES20.glGetAttribLocation(programHandle, "a_Position"); 170 | normalHandle = GLES20.glGetAttribLocation(programHandle, "a_Normal"); 171 | textureCoordinateHandle = GLES20.glGetAttribLocation(programHandle, "a_TexCoordinate"); 172 | miscHandle = GLES20.glGetAttribLocation(programHandle, "a_Misc"); 173 | 174 | Matrix.setIdentityM(mModelMatrix, 0); 175 | Matrix.translateM(mModelMatrix, 0, 0.0f, 0.0f, 5f); 176 | 177 | Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0); 178 | 179 | // Pass in the modelview matrix. 180 | GLES20.glUniformMatrix4fv(mvMatrixHandle, 1, false, mMVPMatrix, 0); 181 | 182 | Matrix.multiplyMM(mTemporaryMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0); 183 | System.arraycopy(mTemporaryMatrix, 0, mMVPMatrix, 0, 16); 184 | 185 | // Pass in the combined matrix. 186 | GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mMVPMatrix, 0); 187 | 188 | // Pass in u_Time 189 | GLES20.glUniform1f(timeHandle, (System.currentTimeMillis() - mStartTime) / 3500f); 190 | 191 | // u_Resolution 192 | GLES20.glUniform2f(resolutionHandle, mWidth, mHeight); 193 | 194 | GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); 195 | GLES20.glEnable(GLES20.GL_BLEND); 196 | } 197 | 198 | public void logFrame() { 199 | frames++; 200 | timePassed = (System.nanoTime() - startTime) / 1_000_000; 201 | if(timePassed >= 10_000) { 202 | Timber.d("fps %d @ %d stars || %f", frames / 10, ParticleSystem.PARTICLE_COUNT, mTime); 203 | frames = 0; 204 | startTime = System.nanoTime(); 205 | } 206 | } 207 | 208 | public void onTouchEvent() { 209 | if (mStart) { 210 | reset(); 211 | } 212 | mStart = !mStart; 213 | mStartTime = System.nanoTime(); 214 | } 215 | 216 | private void reset() { 217 | if (animator != null) { 218 | animator.cancel(); 219 | } 220 | mStartTime = 0; 221 | dt = 0; 222 | t_prev = 0; 223 | } 224 | 225 | 226 | public ParticleSystem getParticleSystem() { 227 | return mParticleSystem; 228 | } 229 | 230 | public void setParticleSystem(final ParticleSystem particleSystem) { 231 | mParticleSystem = particleSystem; 232 | } 233 | 234 | public void queue(Runnable runnable) { 235 | mGlSurfaceView.queueEvent(runnable); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/particlesys/ParticlesGenerator.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.particlesys; 2 | 3 | import com.yalantis.starwarsdemo.util.gl.Const; 4 | import com.yalantis.starwarsdemo.util.gl.math.MathHelper; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.nio.ByteOrder; 8 | import java.nio.FloatBuffer; 9 | 10 | /** 11 | * Started by Artem Kholodnyi on 11/29/15 6:13 PM -- com.yalantis.starwarsdemo.particlesys 12 | */ 13 | public class ParticlesGenerator implements Runnable { 14 | private final ParticleSystemRenderer mRenderer; 15 | 16 | public ParticlesGenerator(ParticleSystemRenderer particleSystemRenderer) { 17 | mRenderer = particleSystemRenderer; 18 | } 19 | 20 | @Override 21 | public void run() { 22 | final FloatBuffer vertexBuffer = makeInterleavedBuffer( 23 | generatePositions(), 24 | generateTextureUv(), 25 | generateMisc(), 26 | ParticleSystem.PARTICLE_COUNT 27 | ); 28 | mRenderer.queue(new Runnable() { 29 | @Override 30 | public void run() { 31 | mRenderer.setParticleSystem(new ParticleSystem(mRenderer, vertexBuffer)); 32 | } 33 | }); 34 | } 35 | 36 | private float[] generatePositions() { 37 | final int size = ParticleSystem.PARTICLE_COUNT * ParticleSystem.POS_DATA_SIZE * 6; 38 | 39 | final float height = 5.0f; 40 | final float width = height * mRenderer.ratio; 41 | 42 | final float[] posData = new float[size]; 43 | 44 | final float z = 0f; 45 | 46 | int offset = 0; 47 | for (int p = 0; p < ParticleSystem.PARTICLE_COUNT; p++) { 48 | final float uc = MathHelper.mix(-width, width, Math.random()); 49 | final float vc = MathHelper.mix(-height, height, Math.random()); 50 | 51 | final float u0 = uc;// + delta; 52 | final float v0 = vc;// + delta; 53 | final float u1 = uc;// - delta; 54 | final float v1 = vc;// - delta; 55 | 56 | final int elementsPerPoint = ParticleSystem.POS_DATA_SIZE; 57 | 58 | // 1---2 59 | // | / | 60 | // 3---4 61 | 62 | final float[] p1 = {u1, v0, z}; 63 | final float[] p2 = {u0, v0, z}; 64 | final float[] p3 = {u1, v1, z}; 65 | final float[] p4 = {u0, v1, z}; 66 | 67 | for (int i = 0; i < elementsPerPoint; i++) 68 | posData[offset++] = p1[i]; 69 | for (int i = 0; i < elementsPerPoint; i++) 70 | posData[offset++] = p3[i]; 71 | for (int i = 0; i < elementsPerPoint; i++) 72 | posData[offset++] = p2[i]; 73 | 74 | for (int i = 0; i < elementsPerPoint; i++) 75 | posData[offset++] = p3[i]; 76 | for (int i = 0; i < elementsPerPoint; i++) 77 | posData[offset++] = p4[i]; 78 | for (int i = 0; i < elementsPerPoint; i++) 79 | posData[offset++] = p2[i]; 80 | } 81 | 82 | return posData; 83 | } 84 | 85 | private float[] generateTextureUv() { 86 | int size = ParticleSystem.TEXTURE_COORDS_DATA_SIZE * 6; 87 | 88 | final float[] thisUvData = new float[size]; 89 | 90 | final float u0 = 0f; 91 | final float v0 = 0f; 92 | final float u1 = 1f; 93 | final float v1 = 1f; 94 | 95 | final int elementsPerPoint = ParticleSystem.TEXTURE_COORDS_DATA_SIZE; 96 | 97 | int offset = 0; 98 | 99 | // 1---2 100 | // | / | 101 | // 3---4 102 | 103 | final float[] p1 = {u1, v0}; 104 | final float[] p2 = {u0, v0}; 105 | final float[] p3 = {u1, v1}; 106 | final float[] p4 = {u0, v1}; 107 | 108 | for (int i = 0; i < elementsPerPoint; i++) 109 | thisUvData[offset++] = p1[i]; 110 | for (int i = 0; i < elementsPerPoint; i++) 111 | thisUvData[offset++] = p3[i]; 112 | for (int i = 0; i < elementsPerPoint; i++) 113 | thisUvData[offset++] = p2[i]; 114 | 115 | for (int i = 0; i < elementsPerPoint; i++) 116 | thisUvData[offset++] = p3[i]; 117 | for (int i = 0; i < elementsPerPoint; i++) 118 | thisUvData[offset++] = p4[i]; 119 | for (int i = 0; i < elementsPerPoint; i++) 120 | thisUvData[offset++] = p2[i]; 121 | 122 | return thisUvData; 123 | } 124 | 125 | private float[] generateMisc() { 126 | final int dataLength = ParticleSystem.PARTICLE_COUNT * ParticleSystem.MISC_DATA_SIZE * 6; 127 | 128 | float[] starData = new float[dataLength]; 129 | 130 | int offset = 0; 131 | for (int i = 0; i < ParticleSystem.PARTICLE_COUNT; i++) { 132 | final float particleSize = MathHelper.mix(0.05f, 0.15f, Math.random()); 133 | float accel = MathHelper.mix(1.1f, 3f, Math.random()); 134 | float rand = (float) Math.random(); 135 | 136 | for (int v = 0; v < 6; v++) { 137 | starData[offset++] = particleSize; 138 | starData[offset++] = accel; 139 | starData[offset++] = rand; 140 | } 141 | } 142 | 143 | return starData; 144 | 145 | } 146 | 147 | 148 | private FloatBuffer makeInterleavedBuffer(float[] posData, 149 | float[] uvData, 150 | float[] miscData, 151 | int numStars) { 152 | 153 | int dataLength = posData.length 154 | + uvData.length * numStars 155 | + miscData.length; 156 | 157 | final FloatBuffer interleavedBuffer = ByteBuffer.allocateDirect(dataLength * Const.BYTES_PER_FLOAT) 158 | .order(ByteOrder.nativeOrder()).asFloatBuffer(); 159 | 160 | int positionOffset = 0, uvOffset = 0, starDataOffset = 0; 161 | 162 | for (int i = 0; i < numStars; i++) { 163 | for (int v = 0; v < 6; v++) { 164 | interleavedBuffer.put(posData, positionOffset, ParticleSystem.POS_DATA_SIZE); 165 | positionOffset += ParticleSystem.POS_DATA_SIZE; 166 | 167 | interleavedBuffer.put(uvData, uvOffset, ParticleSystem.TEXTURE_COORDS_DATA_SIZE); 168 | uvOffset = (uvOffset + ParticleSystem.TEXTURE_COORDS_DATA_SIZE ) % uvData.length; 169 | 170 | interleavedBuffer.put(miscData, starDataOffset, ParticleSystem.MISC_DATA_SIZE); 171 | starDataOffset += ParticleSystem.MISC_DATA_SIZE; 172 | } 173 | } 174 | 175 | interleavedBuffer.position(0); 176 | return interleavedBuffer; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/util/gl/Const.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.util.gl; 2 | 3 | /** 4 | * Started by Artem Kholodnyi on 11/1/15 12:55 PM -- com.yalantis.com.yalantis.starwars.util.gl 5 | */ 6 | public class Const { 7 | 8 | public static final int POSITION_DATA_SIZE = 3; 9 | public static final int NORMALS_DATA_SIZE = 3; 10 | public static final int TEXTURE_COORDS_DATA_SIZE = 2; 11 | public static final int TILE_XY_DATA_SIZE = 3; 12 | 13 | 14 | public static final int STAR_DATA_SIZE = 4; 15 | public static final int BYTES_PER_FLOAT = 4; 16 | public static final int POINTS_PER_TILE = 6; 17 | } 18 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/util/gl/math/MathHelper.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.util.gl.math; 2 | 3 | /** 4 | * Created by Artem Kholodnyi on 11/13/15. 5 | */ 6 | public class MathHelper { 7 | 8 | public static float mix(float x, float y, float a) { 9 | return x * (1 - a) + y * a; 10 | } 11 | 12 | public static float mix(float a, float b , double k) { 13 | return (float) (a * (1 - k) + b * k); 14 | } 15 | 16 | public static float smoothstep(float edge0, float edge1, float x) { 17 | float t = (float) clamp((x - edge0) / (edge1 - edge0), 0f, 1f); 18 | return t * t * (3f - 2f * t); 19 | } 20 | 21 | public static double clamp(float x, float f, double c) { 22 | return Math.max(f, Math.min(x, c)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/view/BrightSideFragment.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.view; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.yalantis.starwarsdemo.R; 6 | import com.yalantis.starwarsdemo.model.User; 7 | 8 | /** 9 | * Created by Artem Kholodnyi on 11/17/15. 10 | */ 11 | public class BrightSideFragment extends SideFragment { 12 | 13 | public static BrightSideFragment newInstance(int centerX, int centerY, boolean appBarExpanded) { 14 | Bundle args = new Bundle(); 15 | args.putInt(ARG_CX, centerX); 16 | args.putInt(ARG_CY, centerY); 17 | args.putBoolean(ARG_SHOULD_EXPAND, appBarExpanded); 18 | 19 | BrightSideFragment frag = newInstance(); 20 | frag.setArguments(args); 21 | 22 | return frag; 23 | } 24 | 25 | public static BrightSideFragment newInstance() { 26 | return new BrightSideFragment(); 27 | } 28 | 29 | @Override 30 | int getTheme() { 31 | return R.style.StarWarsAppThemeLight; 32 | } 33 | 34 | @Override 35 | User getUser() { 36 | return new User(false, "Anakin Skywalker", "Tatooine", "41.9 BBY"); 37 | } 38 | 39 | @Override 40 | public String getTagString() { 41 | return "bright"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/view/DarkSideFragment.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.view; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.yalantis.starwarsdemo.R; 6 | import com.yalantis.starwarsdemo.model.User; 7 | 8 | /** 9 | * Created by Artem Kholodnyi on 11/17/15. 10 | */ 11 | public class DarkSideFragment extends SideFragment { 12 | 13 | public static DarkSideFragment newInstance(int centerX, int centerY, boolean appBarExpanded) { 14 | Bundle args = new Bundle(); 15 | args.putInt(ARG_CX, centerX); 16 | args.putInt(ARG_CY, centerY); 17 | args.putBoolean(ARG_SHOULD_EXPAND, appBarExpanded); 18 | 19 | DarkSideFragment frag = newInstance(); 20 | frag.setArguments(args); 21 | 22 | return frag; 23 | } 24 | 25 | public static DarkSideFragment newInstance() { 26 | return new DarkSideFragment(); 27 | } 28 | 29 | 30 | @Override 31 | int getTheme() { 32 | return R.style.StarWarsAppThemeDark; 33 | } 34 | 35 | @Override 36 | User getUser() { 37 | return new User(true, "Darth Vader", "Tatooine", "41.9 BBY"); 38 | } 39 | 40 | @Override 41 | public String getTagString() { 42 | return "dark"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/view/DemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.view; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | import android.content.pm.ConfigurationInfo; 6 | import android.opengl.GLSurfaceView; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.support.annotation.Nullable; 10 | import android.support.v4.app.Fragment; 11 | import android.support.v4.app.FragmentTransaction; 12 | import android.support.v7.app.AppCompatActivity; 13 | 14 | import com.yalantis.starwarsdemo.R; 15 | import com.yalantis.starwarsdemo.interfaces.DemoActivityInterface; 16 | import com.yalantis.starwarsdemo.interfaces.GreetingFragmentInterface; 17 | import com.yalantis.starwarsdemo.interfaces.TilesRendererInterface; 18 | import com.yalantis.starwarsdemo.particlesys.ParticleSystemRenderer; 19 | 20 | import java.util.List; 21 | 22 | import butterknife.Bind; 23 | import butterknife.ButterKnife; 24 | 25 | /** 26 | * Created by Artem Kholodnyi on 11/11/15. 27 | */ 28 | public class DemoActivity extends AppCompatActivity implements GreetingFragmentInterface, 29 | DemoActivityInterface, TilesRendererInterface { 30 | @Bind(R.id.gl_surface_view) 31 | GLSurfaceView mGlSurfaceView; 32 | 33 | private SideFragment mDarkFragment; 34 | private SideFragment mBrightFragment; 35 | private GreetingsFragment mGreetingsFragment; 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | 41 | setContentView(R.layout.activity_demo); 42 | ButterKnife.bind(this); 43 | 44 | // Check if the system supports OpenGL ES 2.0. 45 | final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); 46 | final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo(); 47 | final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000; 48 | 49 | if (supportsEs2) { 50 | // Request an OpenGL ES 2.0 compatible context. 51 | mGlSurfaceView.setEGLContextClientVersion(2); 52 | 53 | // Set the renderer to our demo renderer, defined below. 54 | ParticleSystemRenderer mRenderer = new ParticleSystemRenderer(mGlSurfaceView); 55 | mGlSurfaceView.setRenderer(mRenderer); 56 | mGlSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); 57 | } else { 58 | throw new UnsupportedOperationException(); 59 | } 60 | 61 | if (savedInstanceState == null) { 62 | showGreetings(); 63 | } 64 | 65 | } 66 | 67 | @Override 68 | protected void onPause() { 69 | super.onPause(); 70 | mGlSurfaceView.onPause(); 71 | } 72 | 73 | @Override 74 | protected void onResume() { 75 | super.onResume(); 76 | mGlSurfaceView.onResume(); 77 | } 78 | 79 | private void showGreetings() { 80 | mGreetingsFragment = GreetingsFragment.newInstance(); 81 | getSupportFragmentManager().beginTransaction() 82 | .setCustomAnimations(R.anim.slide_downward, 0, R.anim.slide_downward, 0) 83 | .add(R.id.container, mGreetingsFragment, "greetings") 84 | .commit(); 85 | } 86 | 87 | @Override 88 | public void onSetupProfileClick() { 89 | new Handler().post(new Runnable() { 90 | @Override 91 | public void run() { 92 | getSupportFragmentManager().beginTransaction() 93 | .setCustomAnimations(R.anim.slide_upward, 0) 94 | .add(R.id.container, BrightSideFragment.newInstance(), "bright") 95 | .commit(); 96 | } 97 | }); 98 | 99 | } 100 | 101 | @Override 102 | public void goToSide(int cx, int cy, boolean appBarExpanded, String side) { 103 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 104 | 105 | 106 | mDarkFragment = DarkSideFragment.newInstance(cx, cy, appBarExpanded); 107 | Fragment fragment; 108 | switch (side) { 109 | case "bright": 110 | fragment = BrightSideFragment.newInstance(cx, cy, appBarExpanded); 111 | break; 112 | case "dark": 113 | fragment = DarkSideFragment.newInstance(cx, cy, appBarExpanded); 114 | break; 115 | default: 116 | throw new IllegalStateException(); 117 | } 118 | ft.add(R.id.container, fragment, side).commit(); 119 | } 120 | 121 | @Override 122 | public void removeAllFragmentExcept(@Nullable String tag) { 123 | List frags = getSupportFragmentManager().getFragments(); 124 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 125 | Fragment frag; 126 | for (int i = 0; i < frags.size(); i++) { 127 | frag = frags.get(i); 128 | if (frag == null) { 129 | continue; 130 | } 131 | if (tag == null || !tag.equals(frag.getTag())) { 132 | ft.remove(frag); 133 | } 134 | } 135 | ft.commit(); 136 | } 137 | 138 | @Override 139 | public void onTilesFinished() { 140 | showGreetings(); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/view/GreetingsFragment.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.view; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import com.yalantis.starwarsdemo.R; 12 | import com.yalantis.starwarsdemo.interfaces.GreetingFragmentInterface; 13 | 14 | import butterknife.Bind; 15 | import butterknife.ButterKnife; 16 | import butterknife.OnClick; 17 | 18 | /** 19 | * Created by Artem Kholodnyi on 11/17/15. 20 | */ 21 | public class GreetingsFragment extends Fragment { 22 | public final static String TAG = GreetingsFragment.class.getCanonicalName(); 23 | 24 | @Bind(R.id.toolbar) 25 | Toolbar mToolbar; 26 | 27 | private GreetingFragmentInterface mListener; 28 | 29 | public static GreetingsFragment newInstance() { 30 | return new GreetingsFragment(); 31 | } 32 | 33 | @Override 34 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 35 | super.onActivityCreated(savedInstanceState); 36 | mListener = (GreetingFragmentInterface) getActivity(); 37 | } 38 | 39 | @Nullable 40 | @Override 41 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 42 | View view = inflater.inflate(R.layout.fragment_greetings, container, false); 43 | ButterKnife.bind(this, view); 44 | return view; 45 | } 46 | 47 | @Override 48 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 49 | super.onViewCreated(view, savedInstanceState); 50 | mToolbar.setNavigationIcon(R.drawable.ic_menu); 51 | } 52 | 53 | @Override 54 | public void onDestroyView() { 55 | super.onDestroyView(); 56 | ButterKnife.unbind(this); 57 | } 58 | 59 | @OnClick(R.id.btn_setup_profile) 60 | void onClick() { 61 | mListener.onSetupProfileClick(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/view/SideFragment.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.view; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ObjectAnimator; 5 | import android.app.Activity; 6 | import android.graphics.Rect; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import android.support.annotation.StyleRes; 10 | import android.support.design.widget.AppBarLayout; 11 | import android.support.design.widget.CollapsingToolbarLayout; 12 | import android.support.v4.app.Fragment; 13 | import android.support.v7.view.ContextThemeWrapper; 14 | import android.support.v7.widget.LinearLayoutManager; 15 | import android.support.v7.widget.RecyclerView; 16 | import android.support.v7.widget.SwitchCompat; 17 | import android.support.v7.widget.Toolbar; 18 | import android.view.LayoutInflater; 19 | import android.view.MenuItem; 20 | import android.view.View; 21 | import android.view.ViewAnimationUtils; 22 | import android.view.ViewGroup; 23 | import android.view.animation.AccelerateInterpolator; 24 | import android.widget.ImageView; 25 | 26 | import com.yalantis.starwars.TilesFrameLayout; 27 | import com.yalantis.starwars.interfaces.TilesFrameLayoutListener; 28 | import com.yalantis.starwarsdemo.R; 29 | import com.yalantis.starwarsdemo.adapter.ProfileAdapter; 30 | import com.yalantis.starwarsdemo.interfaces.DemoActivityInterface; 31 | import com.yalantis.starwarsdemo.interfaces.ProfileAdapterListener; 32 | import com.yalantis.starwarsdemo.interfaces.TilesRendererInterface; 33 | import com.yalantis.starwarsdemo.model.User; 34 | import com.yalantis.starwarsdemo.widget.ClipRevealFrame; 35 | 36 | import butterknife.Bind; 37 | import butterknife.ButterKnife; 38 | import butterknife.OnClick; 39 | 40 | /** 41 | * Created by Artem Kholodnyi on 11/19/15. 42 | */ 43 | public abstract class SideFragment extends Fragment implements ProfileAdapterListener, 44 | TilesFrameLayoutListener { 45 | public static final String ARG_CX = "cx"; 46 | public static final String ARG_CY = "cy"; 47 | public static final String ARG_SHOULD_EXPAND = "should expand"; 48 | private static final long ANIM_DURATION = 250L; 49 | protected float mRadius; 50 | @Bind(R.id.recycler) 51 | RecyclerView mRecycler; 52 | @Bind(R.id.toolbar) 53 | Toolbar mToolbar; 54 | @Bind(R.id.header) 55 | ImageView mHeader; 56 | @Bind(R.id.tessellation_frame_layout) 57 | TilesFrameLayout mTilesFrameLayout; 58 | @Bind(R.id.collapsing_toolbar_layout) 59 | CollapsingToolbarLayout mCollapsingToolbarLayout; 60 | @Bind(R.id.app_bar_layout) 61 | AppBarLayout mAppBarLayout; 62 | private View mRootView; 63 | private Toolbar.OnMenuItemClickListener onMenuItemClickListener = new Toolbar.OnMenuItemClickListener() { 64 | @Override 65 | public boolean onMenuItemClick(final MenuItem item) { 66 | if (R.id.action_close == item.getItemId()) { 67 | doBreak(); 68 | } 69 | return false; 70 | } 71 | }; 72 | private TilesRendererInterface mTilesListener; 73 | private DemoActivityInterface mDemoActivityInterface; 74 | 75 | @Override 76 | public void onResume() { 77 | super.onResume(); 78 | // mTilesFrameLayout.onResume(); 79 | } 80 | 81 | @Override 82 | public void onPause() { 83 | super.onPause(); 84 | mTilesFrameLayout.onPause(); 85 | } 86 | 87 | @Override 88 | public void onAttach(Activity activity) { 89 | super.onAttach(activity); 90 | if (activity instanceof TilesRendererInterface) { 91 | if (activity instanceof TilesRendererInterface) { 92 | mTilesListener = (TilesRendererInterface) activity; 93 | } 94 | if (activity instanceof DemoActivityInterface) { 95 | mDemoActivityInterface = (DemoActivityInterface) activity; 96 | } 97 | } 98 | } 99 | 100 | abstract @StyleRes int getTheme(); 101 | 102 | protected Animator createCheckoutRevealAnimator(final ClipRevealFrame view, int x, int y, float startRadius, float endRadius) { 103 | setMenuVisibility(false); 104 | Animator retAnimator; 105 | mRadius = endRadius; 106 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 107 | retAnimator = ViewAnimationUtils.createCircularReveal(view, x, y, startRadius, endRadius); 108 | } else { 109 | view.setClipOutLines(true); 110 | view.setClipCenter(x, y); 111 | view.setClipRadius(startRadius); 112 | 113 | retAnimator = ObjectAnimator.ofFloat(view, "clipRadius", startRadius, endRadius); 114 | } 115 | retAnimator.setDuration(ANIM_DURATION); 116 | retAnimator.addListener(new Animator.AnimatorListener() { 117 | @Override 118 | public void onAnimationStart(Animator animation) { 119 | 120 | } 121 | 122 | @Override 123 | public void onAnimationEnd(Animator animation) { 124 | view.setClipOutLines(false); 125 | removeOldSideFragment(); 126 | } 127 | 128 | @Override 129 | public void onAnimationCancel(Animator animation) { 130 | 131 | } 132 | 133 | @Override 134 | public void onAnimationRepeat(Animator animation) { 135 | 136 | } 137 | }); 138 | 139 | retAnimator.setInterpolator(new AccelerateInterpolator(2.0f)); 140 | return retAnimator; 141 | } 142 | 143 | private void removeOldSideFragment() { 144 | if (mDemoActivityInterface != null) { 145 | mDemoActivityInterface.removeAllFragmentExcept(getTagString()); 146 | } 147 | } 148 | 149 | @Override 150 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 151 | mRootView = inflater.cloneInContext(new ContextThemeWrapper(getContext(), getTheme())) 152 | .inflate(R.layout.fragment_side, container, false); 153 | final Bundle args = getArguments(); 154 | if (args != null) { 155 | mRootView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 156 | @Override 157 | public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, 158 | int oldRight, int oldBottom) { 159 | v.removeOnLayoutChangeListener(this); 160 | int cx = args.getInt("cx"); 161 | int cy = args.getInt("cy"); 162 | // get the hypothenuse so the mRadius is from one corner to the other 163 | float radius = (float) Math.hypot(right, bottom); 164 | 165 | // Hardware-supported clipPath() 166 | // http://developer.android.com/guide/topics/graphics/hardware-accel.html 167 | if (Build.VERSION.SDK_INT >= 18) { 168 | Animator reveal = createCheckoutRevealAnimator((ClipRevealFrame) v, cx, cy, 28f, radius); 169 | reveal.start(); 170 | } else { 171 | removeOldSideFragment(); 172 | } 173 | } 174 | }); 175 | } 176 | 177 | ButterKnife.bind(this, mRootView); 178 | return mRootView; 179 | } 180 | 181 | 182 | 183 | 184 | 185 | @Override 186 | public void onViewCreated(View view, Bundle savedInstanceState) { 187 | super.onViewCreated(view, savedInstanceState); 188 | mTilesFrameLayout.setOnAnimationFinishedListener(this); 189 | mRecycler.setLayoutManager(new LinearLayoutManager(getContext())); 190 | mRecycler.setAdapter(new ProfileAdapter(getContext(), getUser(), this)); 191 | setUpToolbar(mToolbar); 192 | mHeader.setImageResource(getUser().getPhotoRes()); 193 | if (getArguments() != null) { 194 | mAppBarLayout.setExpanded(getArguments().getBoolean(ARG_SHOULD_EXPAND), false); 195 | } 196 | } 197 | 198 | @Override 199 | public void onDetach() { 200 | super.onDetach(); 201 | mTilesListener = null; 202 | mDemoActivityInterface = null; 203 | } 204 | 205 | private void setUpToolbar(final Toolbar toolbar) { 206 | toolbar.setNavigationIcon(R.drawable.ic_menu); 207 | toolbar.setTitle(R.string.settings); 208 | toolbar.inflateMenu(R.menu.menu_star_wars); 209 | toolbar.setOnMenuItemClickListener(onMenuItemClickListener); 210 | } 211 | 212 | abstract User getUser(); 213 | 214 | 215 | 216 | @Override 217 | public void onDestroyView() { 218 | super.onDestroyView(); 219 | ButterKnife.unbind(this); 220 | } 221 | 222 | @Override 223 | public void onSideSwitch(SwitchCompat v) { 224 | Rect rect = new Rect(); 225 | v.getGlobalVisibleRect(rect); 226 | final int cy = rect.centerY() - getStatusBarHeight(); 227 | final int halfThumbWidth = v.getThumbDrawable().getIntrinsicWidth() / 2; 228 | final int cx; 229 | 230 | if (this instanceof BrightSideFragment && v.isChecked()) { 231 | cx = rect.right - halfThumbWidth; 232 | postGoToSide(cy, cx, "dark"); 233 | } else if (!v.isChecked()){ 234 | cx = rect.left + halfThumbWidth; 235 | postGoToSide(cy, cx, "bright"); 236 | } 237 | } 238 | 239 | private void postGoToSide(final int cy, final int cx, final String side) { 240 | new android.os.Handler().post(new Runnable() { 241 | @Override 242 | public void run() { 243 | if (mDemoActivityInterface != null) { 244 | mDemoActivityInterface.goToSide(cx, cy, isAppBarExpanded(), side); 245 | } 246 | } 247 | }); 248 | } 249 | 250 | private boolean isAppBarExpanded() { 251 | return mAppBarLayout != null && mAppBarLayout.getBottom() == mAppBarLayout.getHeight(); 252 | } 253 | 254 | public int getStatusBarHeight() { 255 | int result = 0; 256 | int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); 257 | if (resourceId > 0) { 258 | result = getResources().getDimensionPixelSize(resourceId); 259 | } 260 | return result; 261 | } 262 | 263 | public void doBreak() { 264 | if (mDemoActivityInterface != null) { 265 | mDemoActivityInterface.removeAllFragmentExcept(getTagString()); 266 | } 267 | if (mTilesFrameLayout != null) { 268 | mTilesFrameLayout.startAnimation(); 269 | } 270 | } 271 | 272 | @Override 273 | public void onAnimationFinished() { 274 | if (mTilesListener != null) { 275 | mTilesListener.onTilesFinished(); 276 | } 277 | } 278 | 279 | public abstract String getTagString(); 280 | 281 | @OnClick(R.id.btn_save) 282 | void onClick() { 283 | doBreak(); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/widget/BackgroundDrawableSwitchCompat.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.widget; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Rect; 8 | import android.graphics.drawable.Drawable; 9 | import android.graphics.drawable.LevelListDrawable; 10 | import android.support.annotation.IntRange; 11 | import android.support.v4.content.ContextCompat; 12 | import android.support.v7.widget.SwitchCompat; 13 | import android.util.AttributeSet; 14 | import android.view.animation.Animation; 15 | import android.view.animation.LinearInterpolator; 16 | 17 | import com.yalantis.starwarsdemo.R; 18 | 19 | /** 20 | * Created by Artem Kholodnyi on 11/17/15. 21 | */ 22 | public class BackgroundDrawableSwitchCompat extends SwitchCompat { 23 | public static final int OPAQUE = 255; 24 | public static final int TRANSPARENT = 0; 25 | 26 | // Duration taken from SwitchCompat.java 27 | public static final int THUMB_ANIMATION_DURATION = 250; 28 | 29 | private int mFirstDrawableAlpha; 30 | private int mSecondDrawableAlpha; 31 | private TwoStatesTrackDrawable mTwoStatesDrawable; 32 | private ValueAnimator animator; 33 | 34 | public BackgroundDrawableSwitchCompat(Context context) { 35 | super(context); 36 | init(context); 37 | } 38 | 39 | public BackgroundDrawableSwitchCompat(Context context, AttributeSet attrs) { 40 | super(context, attrs); 41 | init(context); 42 | } 43 | 44 | public BackgroundDrawableSwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) { 45 | super(context, attrs, defStyleAttr); 46 | init(context); 47 | } 48 | 49 | private void init(Context context) { 50 | Drawable offDrawable = ContextCompat.getDrawable(context, R.drawable.track_off); 51 | Drawable onDrawable = ContextCompat.getDrawable(context, R.drawable.track_on); 52 | mTwoStatesDrawable = new TwoStatesTrackDrawable(offDrawable, onDrawable); 53 | setTrackDrawable(mTwoStatesDrawable); 54 | } 55 | 56 | @Override 57 | public void setChecked(boolean checked) { 58 | super.setChecked(checked); 59 | if (mTwoStatesDrawable != null) { 60 | startAnimation(checked); 61 | } 62 | } 63 | 64 | public void setCheckedImmediate(boolean checked) { 65 | super.setChecked(checked); 66 | mTwoStatesDrawable.setBlend(checked ? OPAQUE : TRANSPARENT); 67 | } 68 | 69 | private class TwoStatesTrackDrawable extends LevelListDrawable { 70 | private final Drawable mFirstDrawable; 71 | private final Drawable mSecondDrawable; 72 | private Animation mBackgroundAnimator; 73 | 74 | public TwoStatesTrackDrawable(Drawable firstDrawable, Drawable secondDrawable) { 75 | mFirstDrawable = firstDrawable; 76 | mSecondDrawable = secondDrawable; 77 | } 78 | 79 | @Override 80 | public void draw(Canvas canvas) { 81 | mFirstDrawable.setAlpha(mFirstDrawableAlpha); 82 | mSecondDrawable.setAlpha(mSecondDrawableAlpha); 83 | int paddingX = getThumbDrawable().getIntrinsicWidth() / 4; 84 | int paddingY = getThumbDrawable().getIntrinsicHeight() / 4; 85 | Rect rect = new Rect(paddingX, paddingY, canvas.getWidth() - paddingX, canvas.getHeight() - paddingY); 86 | 87 | mFirstDrawable.setBounds(rect); 88 | mSecondDrawable.setBounds(rect); 89 | 90 | mFirstDrawable.draw(canvas); 91 | mSecondDrawable.draw(canvas); 92 | } 93 | 94 | @Override 95 | public void setAlpha(int alpha) { 96 | 97 | } 98 | 99 | @Override 100 | public void setColorFilter(ColorFilter colorFilter) { 101 | 102 | } 103 | 104 | @Override 105 | public int getOpacity() { 106 | return 0; 107 | } 108 | 109 | private void setBlend(@IntRange(from = TRANSPARENT, to = OPAQUE) int k) { 110 | mFirstDrawableAlpha = OPAQUE - k; 111 | mSecondDrawableAlpha = k; 112 | invalidate(); 113 | } 114 | } 115 | 116 | private void startAnimation(boolean firstToSecond) { 117 | if (animator != null) { 118 | animator.cancel(); 119 | } 120 | animator = ValueAnimator.ofInt(firstToSecond ? OPAQUE : TRANSPARENT, firstToSecond ? TRANSPARENT : OPAQUE); 121 | animator.setDuration(THUMB_ANIMATION_DURATION); 122 | animator.setInterpolator(new LinearInterpolator()); 123 | // animator.setRepeatMode(ValueAnimator.RESTART); 124 | // animator.setRepeatCount(ValueAnimator.INFINITE); 125 | 126 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 127 | @Override 128 | public void onAnimationUpdate(ValueAnimator animation) { 129 | mFirstDrawableAlpha = (int) animation.getAnimatedValue(); 130 | mSecondDrawableAlpha = OPAQUE - (int) animation.getAnimatedValue(); 131 | invalidate(); 132 | } 133 | }); 134 | 135 | animator.start(); 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/widget/CenterTopImageView.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Matrix; 5 | import android.util.AttributeSet; 6 | import android.widget.ImageView; 7 | 8 | /** 9 | * Created by Artem Kholodnyi on 11/23/15. 10 | */ 11 | public class CenterTopImageView extends ImageView { 12 | private Matrix matrix = new Matrix(); 13 | 14 | public CenterTopImageView(Context context) { 15 | super(context); 16 | init(); 17 | } 18 | 19 | public CenterTopImageView(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | init(); 22 | } 23 | 24 | public CenterTopImageView(Context context, AttributeSet attrs, int defStyleAttr) { 25 | super(context, attrs, defStyleAttr); 26 | } 27 | 28 | private void init() { 29 | setScaleType(ScaleType.MATRIX); 30 | } 31 | 32 | @Override 33 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 34 | super.onLayout(changed, left, top, right, bottom); 35 | if (getDrawable() != null) { 36 | float k = (right - left) / (float)getDrawable().getIntrinsicWidth(); 37 | matrix.setScale(k, k); 38 | setImageMatrix(matrix); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /demo/src/main/java/com/yalantis/starwarsdemo/widget/ClipRevealFrame.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.starwarsdemo.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Path; 6 | import android.util.AttributeSet; 7 | import android.widget.FrameLayout; 8 | 9 | /** 10 | * Created by Artem Kholodnyi on 11/17/15. 11 | */ 12 | public class ClipRevealFrame extends FrameLayout { 13 | private Path mRevealPath; 14 | boolean mClipOutlines; 15 | float mCenterX; 16 | float mCenterY; 17 | float mRadius; 18 | 19 | public ClipRevealFrame(Context context) { 20 | super(context); 21 | init(); 22 | } 23 | 24 | public ClipRevealFrame(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | init(); 27 | } 28 | 29 | public ClipRevealFrame(Context context, AttributeSet attrs, int defStyleAttr) { 30 | super(context, attrs, defStyleAttr); 31 | init(); 32 | } 33 | 34 | private void init(){ 35 | mRevealPath = new Path(); 36 | mClipOutlines = false; 37 | setWillNotDraw(false); 38 | } 39 | 40 | public void setClipOutLines(boolean shouldClip){ 41 | mClipOutlines = shouldClip; 42 | } 43 | 44 | public void setClipCenter(final int x, final int y){ 45 | mCenterX = x; 46 | mCenterY = y; 47 | } 48 | 49 | public void setClipRadius(final float radius){ 50 | mRadius = radius; 51 | invalidate(); 52 | } 53 | 54 | @Override 55 | public void draw(Canvas canvas) { 56 | if(!mClipOutlines){ 57 | super.draw(canvas); 58 | return; 59 | } 60 | final int state = canvas.save(); 61 | mRevealPath.reset(); 62 | mRevealPath.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW); 63 | canvas.clipPath(mRevealPath); 64 | super.draw(canvas); 65 | canvas.restoreToCount(state); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /demo/src/main/res/anim/slide_downward.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /demo/src/main/res/anim/slide_upward.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/anakin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-hdpi/anakin.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/darth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-hdpi/darth.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-hdpi/ic_close.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/star_wars_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-hdpi/star_wars_logo.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/track_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-hdpi/track_off.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-mdpi/anakin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-mdpi/anakin.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-v21/button_setup_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/anakin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xhdpi/anakin.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/darth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xhdpi/darth.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xhdpi/ic_close.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/star_wars_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xhdpi/star_wars_logo.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/track_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xhdpi/track_off.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/darth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xxhdpi/darth.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xxhdpi/ic_close.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xxhdpi/ic_menu.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/star_wars_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xxhdpi/star_wars_logo.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/track_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable-xxhdpi/track_off.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable/btn_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable/button_setup_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable/leia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable/leia.jpg -------------------------------------------------------------------------------- /demo/src/main/res/drawable/star_wars_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable/star_wars_logo.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable/track_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable/track_off.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable/track_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/StarWars.Android/a947b5a5b59e4543004af6c6ca08d5fa0451233a/demo/src/main/res/drawable/track_on.png -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_greetings.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 16 | 24 | 25 | 41 | 42 |