├── .gitignore ├── .idea ├── assetWizardSettings.xml ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── Artwork ├── PhoneAndScreen.gif └── Transition.gif ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── no │ │ └── danielzeller │ │ └── blurbehind │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── no │ │ │ └── danielzeller │ │ │ └── blurbehind │ │ │ ├── GridSpaces.kt │ │ │ ├── MainActivity.kt │ │ │ ├── UnsplashGridAdapter.kt │ │ │ ├── UnsplashViewModel.kt │ │ │ ├── animation │ │ │ ├── CardTransitionHelper.kt │ │ │ └── LoaderImageView.kt │ │ │ ├── extensions │ │ │ └── extensions.kt │ │ │ ├── fragments │ │ │ ├── DetailsFragment.kt │ │ │ └── DialogFragment.kt │ │ │ └── model │ │ │ └── UnsplashItem.kt │ └── res │ │ ├── drawable-xxhdpi │ │ └── ic_author.png │ │ ├── drawable │ │ ├── black_gradient.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ └── rounded_corners_drawable.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── card1.xml │ │ ├── card2.xml │ │ ├── card3.xml │ │ ├── card4.xml │ │ ├── fragment_details.xml │ │ └── fragment_dialog.xml │ │ ├── mipmap-anydpi │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── no │ └── danielzeller │ └── blurbehind │ └── ExampleUnitTest.kt ├── blurbehindlib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── no │ │ └── danielzeller │ │ └── blurbehindlib │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── no │ │ │ └── danielzeller │ │ │ └── blurbehindlib │ │ │ ├── BlurBehindLayout.kt │ │ │ ├── ScreenRectUtil.kt │ │ │ ├── opengl │ │ │ ├── RenderTexture.kt │ │ │ ├── ShaderHelper.kt │ │ │ ├── ShaderProgram.kt │ │ │ ├── SpriteMesh.kt │ │ │ ├── TextResourceReader.kt │ │ │ ├── TextureShaderProgram.kt │ │ │ ├── VertexArray.kt │ │ │ └── ViewSurfaceTexture.kt │ │ │ └── renderers │ │ │ ├── CommonRenderer.kt │ │ │ ├── GLSurfaceViewRenderer.kt │ │ │ └── TextureViewRenderer.kt │ └── res │ │ ├── raw │ │ ├── gauss_2_pass_horizontal.glsl │ │ ├── gauss_2_pass_vertical.glsl │ │ ├── texture_and_mask_frag.glsl │ │ ├── texture_frag.glsl │ │ └── vertex_shader.glsl │ │ └── values │ │ ├── attrs.xml │ │ ├── dimens.xml │ │ └── strings.xml │ └── test │ └── java │ └── no │ └── danielzeller │ └── blurbehindlib │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/assetWizardSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielzeller/Blur-LIB-Android/20b685fa0d37285124bdb17b009024ec69751965/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Artwork/PhoneAndScreen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielzeller/Blur-LIB-Android/20b685fa0d37285124bdb17b009024ec69751965/Artwork/PhoneAndScreen.gif -------------------------------------------------------------------------------- /Artwork/Transition.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielzeller/Blur-LIB-Android/20b685fa0d37285124bdb17b009024ec69751965/Artwork/Transition.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Authors: 2 | Daniel Zeller 3 | 4 | The MIT License (MIT) 5 | Copyright (c) 2016 Daniel Zeller AS (http://danielzeller.no) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blur-LIB-Android 2 | 3 | A library for Blurring the background of a View. 4 | 5 | [](https://youtu.be/sYPqS0px61Q) 6 | 7 | [See demo on youtube](https://youtu.be/sYPqS0px61Q) 8 | 9 | ## How it works 10 | The blurring is really fast since everything is fully hardware accelerated. The background View that is to be blurred is rendered into a SurfaceTexture using Surface.lockHardwareCanvas(). 11 | The SurfaceTexture is then blurred using a Guassian blur algorithm and rendered in a SurfaceView or TextureView using OpenGL. 12 | 13 | 14 | ## Download 15 | Via Gradle 16 | 17 | ``` 18 | implementation 'no.danielzeller.blurbehindlib:blurbehindlib:1.0.0' 19 | ``` 20 | or Maven 21 | ``` 22 | 23 | no.danielzeller.blurbehindlib 24 | blurbehindlib 25 | 1.0.0 26 | pom 27 | 28 | ``` 29 | 30 | ## Basics 31 | Create a BlurBehindLayout from XML 32 | 33 | ```xml 34 | 45 | 46 | 47 | ``` 48 | 49 | Then you need to setup the View that is behind the BlurBehindLayout (the one that will be blurred). 50 | 51 | ```kotlin 52 | blurBehindLayout.viewBehind = viewToBlur 53 | ``` 54 | 55 | [](https://youtu.be/sYPqS0px61Q) 56 | 57 | #### Blur radius 58 | 59 | 60 | 61 | 62 | 63 |
app:blurRadius = "100.0"
blurBehindLayout.blurRadius = 100.f
64 | Determines how strong the blur is. Should be a float value between 0f-200f. Default is 40f. 65 | 66 | 67 | #### Update mode 68 | 69 | 70 | 71 | 72 | 73 |
app:updateMode = "continuously"
blurBehindLayout.updateMode = UpdateMode.CONTINUOUSLY
74 | Determines how the BlurBehindLayout is updated. When updateMode is UpdateMode.CONTINUOUSLY, the renderer is called repeatedly to re-render the scene. 75 | When updateMode is UpdateMode.ON_SCROLL, the renderer only renders when a View is Scrolled. 76 | When updateMode is UpdateMode.MANUALLY, the renderer only renders when the surface is created and when updateForMilliSeconds(..) is called manually. This is useful when animating the background or during a transition. 77 | 78 | 79 | #### Use TextureView 80 | ``` 81 | app:useTextureView = "true" 82 | ``` 83 | This can only be changed in the constructor of the BlurBehindLayout either from xml or using the regular constructor from code. Default value is false. Using TextureView should only be used when SurfaceView is'nt an option, either because the Z-ordering breaks or if you animate the BlurBehindLayout's alpha value. Using TextureView instead of SurfaceView has a small impact on performance. 84 | 85 | 86 | #### Blur texture scale 87 | ``` 88 | app:blurTextureScale = "0.5" 89 | ``` 90 | Should be a value between 0.1f-1f. It's recommended to downsample at least to 0.5f. The scale has a big impact on performance, so try keeping it as low as possible. Default is 0.4f. This can only be set in the constructor of the BlurBehindLayout either from xml or using the regular constructor from code. 91 | 92 | 93 | #### Padding vertical 94 | ``` 95 | app:blurPaddingVertical = "50dp" 96 | ``` 97 | You can use this to make the Blur Texture larger than the BlurBehindLayout in the vertical direction. For instance when the background View is scrolled up and down it looks better with a padding, because it reduces flicker when new pixels enter the blurred area. 98 | 99 | 100 | #### Use child as alpha mask 101 | ``` 102 | app:useChildAlphaAsMask = "true" 103 | ``` 104 | When this is true the first child View of the BlurBehindLayout is rendered into a texture. The alpha value of that texture is then used as mask for the Blur texture. When useChildAlphaAsMask is true, useTextureView will be forced to true as well in order to support transparency. 105 | This effect can be used for creating text with blurred background and so on. See the DialogFragment for an example. 106 | 107 | 108 | 109 | ## Contact 110 | 111 | You can reach me on Twitter as [@zellah](https://twitter.com/zellah) or [email](mailto:hello@danielzeller.no). 112 | 113 | 114 | ## Who's behind this? 115 | 116 | Developed by Daniel Zeller - [danielzeller.no](http://danielzeller.no/), a freelance developer situated in Oslo, Norway. 117 | 118 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "no.danielzeller.blurbehind" 11 | minSdkVersion 26 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | def lifecycle_version = "1.1.1" 28 | implementation "android.arch.lifecycle:viewmodel:$lifecycle_version" 29 | implementation "android.arch.lifecycle:extensions:$lifecycle_version" 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 31 | implementation 'com.android.support:appcompat-v7:28.0.0' 32 | implementation 'com.android.support:design:28.0.0' 33 | implementation 'com.android.support:support-v4:28.0.0' 34 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 37 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 38 | implementation project(path: ':blurbehindlib') 39 | implementation 'com.squareup.picasso:picasso:2.71828' 40 | } 41 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/no/danielzeller/blurbehind/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("no.danielzeller.blurbehind", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/GridSpaces.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind 2 | 3 | import android.graphics.Rect 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.View 6 | 7 | class GridSpaces(val space: Int, private val topPadding: Int, private val bottomPadding: Int) : RecyclerView.ItemDecoration() { 8 | 9 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { 10 | val position = parent.getChildAdapterPosition(view) 11 | 12 | outRect.top = space 13 | outRect.bottom = space 14 | outRect.left = space 15 | outRect.right = space 16 | 17 | //Padding for small grid items. Don't do this at home kids. 18 | val adapter = parent.adapter!! 19 | if (adapter.getItemViewType(position) == R.layout.card4) { 20 | if (adapter.getItemViewType(position - 1) == R.layout.card4) { 21 | outRect.left = space / 2 22 | } else { 23 | outRect.right = space / 2 24 | } 25 | } 26 | 27 | //Padding top first item 28 | if (position == 0) { 29 | outRect.top = topPadding 30 | } 31 | 32 | //Padding bottom last item 33 | if (position == adapter.itemCount - 1) { 34 | outRect.bottom = bottomPadding 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind 2 | 3 | import android.arch.lifecycle.ViewModelProviders 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import android.support.v7.widget.GridLayoutManager 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.FrameLayout 10 | import kotlinx.android.synthetic.main.activity_main.* 11 | import no.danielzeller.blurbehind.fragments.DetailsFragment 12 | import no.danielzeller.blurbehind.fragments.DialogFragment 13 | import no.danielzeller.blurbehind.fragments.ORIGIN_VIEW_TAG 14 | import no.danielzeller.blurbehind.model.UnsplashItem 15 | import android.content.Intent 16 | import android.net.Uri 17 | 18 | 19 | const val DETAILS_FRAGMENT_TAG = "details_fragment_tag" 20 | const val DIALOG_FRAGMENT_TAG = "dialog_fragment_tag" 21 | private const val UNSPLASH_RANDOM_URL = "https://source.unsplash.com/960x540?" 22 | private const val CARDS_COUNT = 15 23 | 24 | class MainActivity : AppCompatActivity() { 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | setContentView(R.layout.activity_main) 29 | setupBlurBiews() 30 | setupRecyclerView() 31 | appBarDimmer.layoutParams = getStatusBarHeightParams() 32 | } 33 | 34 | private fun setupBlurBiews() { 35 | appBarBlurLayout.viewBehind = viewToBlur 36 | navigationBarBlurLayout.viewBehind = viewToBlur 37 | } 38 | 39 | private fun setupRecyclerView() { 40 | val gridLayoutManager = GridLayoutManager(this, 2) 41 | 42 | val viewModel = ViewModelProviders.of(this).get(UnsplashViewModel::class.java) 43 | viewModel.createPicasso(this) 44 | val unsplashGridAdapter = UnsplashGridAdapter(createUnsplashItems(), viewModel, listOf(cardClickedUnit, textClickedAction1, textClickedAction2, textClickedAction3)) 45 | 46 | gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { 47 | override fun getSpanSize(p0: Int): Int { 48 | if (unsplashGridAdapter.getItemViewType(p0) == R.layout.card4) { 49 | return 1 50 | } 51 | return 2 52 | } 53 | } 54 | recyclerView.addItemDecoration(GridSpaces(resources.getDimension(R.dimen.grid_item_horizontal_space).toInt(), resources.getDimension(R.dimen.grid_top_padding).toInt(), resources.getDimension(R.dimen.grid_bottom_padding).toInt())) 55 | recyclerView.layoutManager = gridLayoutManager 56 | recyclerView.adapter = unsplashGridAdapter 57 | } 58 | 59 | private fun createUnsplashItems(): ArrayList { 60 | val items = ArrayList() 61 | 62 | val cardsLayouts = intArrayOf(R.layout.card2, R.layout.card3, R.layout.card4, R.layout.card4, R.layout.card1) 63 | val headings = resources.getStringArray(R.array.headings) 64 | val subHeadings = resources.getStringArray(R.array.sub_headings) 65 | val articleContent = resources.getStringArray(R.array.articles_content) 66 | val searchTerm = resources.getStringArray(R.array.image_search_term) 67 | 68 | var cardTypeIndex = 0 69 | for (i in 0 until CARDS_COUNT) { 70 | items.add(UnsplashItem(UNSPLASH_RANDOM_URL + searchTerm[i], headings[i], subHeadings[i], cardsLayouts[cardTypeIndex], articleContent[i], getClickUnit(cardsLayouts[cardTypeIndex]))) 71 | 72 | cardTypeIndex += 1 73 | if (cardTypeIndex == cardsLayouts.size) cardTypeIndex = 0 74 | } 75 | return items 76 | } 77 | 78 | private fun getStatusBarHeightParams(): FrameLayout.LayoutParams { 79 | 80 | val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") 81 | val margin = resources.getDimensionPixelSize(resourceId) 82 | val layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) 83 | layoutParams.topMargin = margin 84 | return layoutParams 85 | } 86 | 87 | private val textClickedAction1: (() -> Unit) = { 88 | if (!isDialogVisible()) { 89 | val dialogfragment = DialogFragment() 90 | supportFragmentManager.beginTransaction().add(R.id.overlayFragmentContainer, dialogfragment, DIALOG_FRAGMENT_TAG).commitNow() 91 | } 92 | } 93 | 94 | private val textClickedAction2: (() -> Unit) = { 95 | val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/danielzeller")) 96 | startActivity(browserIntent) 97 | } 98 | 99 | private val textClickedAction3: (() -> Unit) = { 100 | val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://danielzeller.no")) 101 | startActivity(browserIntent) 102 | } 103 | 104 | private val cardClickedUnit: ((itemView: View, item: UnsplashItem) -> Unit) = { itemView: View, item: UnsplashItem -> 105 | if (!isDialogVisible()) { 106 | val detailsFragment = DetailsFragment.newInstance(itemView, item) 107 | supportFragmentManager.beginTransaction().add(R.id.overlayFragmentContainer, detailsFragment, DETAILS_FRAGMENT_TAG).commitNow() 108 | itemView.visibility = View.INVISIBLE 109 | itemView.tag = ORIGIN_VIEW_TAG 110 | } 111 | } 112 | private var texClickedIndex = 0 113 | 114 | private fun getClickUnit(clickType: Int): Int { 115 | if (clickType == R.layout.card1) { 116 | texClickedIndex += 1 117 | return texClickedIndex 118 | } 119 | return 0 120 | } 121 | 122 | private fun isDialogVisible() = 123 | supportFragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null 124 | 125 | 126 | override fun onBackPressed() { 127 | var exitApp = true 128 | var fragment = supportFragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) 129 | if (fragment != null) { 130 | (fragment as DialogFragment).exitAnimateAndRemove() 131 | exitApp = false 132 | } 133 | 134 | fragment = supportFragmentManager.findFragmentByTag(DETAILS_FRAGMENT_TAG) 135 | if (fragment != null) { 136 | val detailsFragment = fragment as DetailsFragment 137 | if (!detailsFragment.isExitAnimating) { 138 | detailsFragment.exitAnimateAndRemove() 139 | exitApp = false 140 | } else { 141 | detailsFragment.cancelAllRunningAnimations() 142 | } 143 | } 144 | 145 | if (exitApp) { 146 | super.onBackPressed() 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/UnsplashGridAdapter.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind 2 | 3 | import android.animation.ValueAnimator 4 | import android.graphics.Bitmap 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.TextView 10 | import com.squareup.picasso.Callback 11 | import kotlinx.android.synthetic.main.card2.view.* 12 | import no.danielzeller.blurbehind.animation.CLIP_ANIM_DURATION 13 | import no.danielzeller.blurbehind.animation.LoaderImageView 14 | import no.danielzeller.blurbehind.animation.scaleProgressBarInterpolator 15 | import no.danielzeller.blurbehind.extensions.interpolate 16 | import no.danielzeller.blurbehind.extensions.onUpdate 17 | import no.danielzeller.blurbehind.model.UnsplashItem 18 | import java.lang.Exception 19 | import java.lang.ref.WeakReference 20 | 21 | 22 | class UnsplashGridAdapter(val items: List, private val viewModel: UnsplashViewModel, private val clickUnits: List) : RecyclerView.Adapter() { 23 | 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 25 | val v = LayoutInflater.from(parent.context).inflate(viewType, parent, false) 26 | 27 | if (viewType == R.layout.card1) { 28 | return TextOnlyViewHolder(v) 29 | } 30 | return CardViewHolder(v) 31 | } 32 | 33 | override fun getItemCount(): Int { 34 | return items.count() 35 | } 36 | 37 | override fun getItemViewType(position: Int): Int { 38 | return items[position].layoutID 39 | } 40 | 41 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 42 | val item = items[position] 43 | val viewHolder = holder as TextOnlyViewHolder 44 | 45 | viewHolder.subHeading.text = item.subHeading 46 | viewHolder.heading.text = item.heading 47 | 48 | if (holder is CardViewHolder) { 49 | setupImageView(holder, item) 50 | setupCardOnClickListener(holder, item) 51 | } else { 52 | setupTextOnClickListener(holder, item) 53 | } 54 | } 55 | 56 | private fun setupCardOnClickListener(viewHolder: CardViewHolder, item: UnsplashItem) { 57 | viewHolder.itemView.setOnClickListener { 58 | if (!viewHolder.image.isLoaderVisible) { 59 | (clickUnits[0] as ((itemView: View, item: UnsplashItem) -> Unit)).invoke(viewHolder.itemView, item) 60 | } 61 | } 62 | } 63 | 64 | private fun setupTextOnClickListener(viewHolder: TextOnlyViewHolder, item: UnsplashItem) { 65 | viewHolder.itemView.setOnClickListener { 66 | (clickUnits[item.clickUnitIndex] as (() -> Unit)).invoke() 67 | } 68 | } 69 | 70 | private fun setupImageView(viewHolder: CardViewHolder, item: UnsplashItem) { 71 | 72 | val bitmap = viewModel.picassoCache.get(item.imageUrl + "\n") 73 | viewHolder.cancelAnimations() 74 | if (bitmap == null) { 75 | loadImage(viewHolder, item) 76 | } else { 77 | viewHolder.image.setImageBitmap(bitmap) 78 | viewHolder.image.isLoaderVisible = false 79 | } 80 | } 81 | 82 | private fun loadImage(viewHolder: CardViewHolder, item: UnsplashItem) { 83 | 84 | viewHolder.image.isLoaderVisible = true 85 | viewHolder.image.cancelIntroAnim() 86 | viewHolder.setUpForTextAnim() 87 | 88 | val viewHolderRef = WeakReference(viewHolder) 89 | viewModel.picasso.load(item.imageUrl).config(Bitmap.Config.HARDWARE).into(viewHolder.image, createOnImageLoadFinishedCallback(viewHolderRef)) 90 | } 91 | 92 | private fun createOnImageLoadFinishedCallback(viewHolderRef: WeakReference): Callback { 93 | return object : Callback { 94 | override fun onSuccess() { 95 | if (viewHolderRef.get() != null) { 96 | val viewHolder = viewHolderRef.get()!! 97 | 98 | viewHolder.image.introAnimate() 99 | 100 | val translateText = ValueAnimator.ofFloat(viewHolder.image.width.toFloat() / 3f, 0f).setDuration(CLIP_ANIM_DURATION).interpolate(scaleProgressBarInterpolator).onUpdate { value -> 101 | viewHolder.heading.translationY = value as Float 102 | viewHolder.subHeading.translationY = value 103 | } 104 | translateText.start() 105 | viewHolder.translateTextAnim = translateText 106 | } 107 | } 108 | 109 | override fun onError(e: Exception?) {} 110 | } 111 | } 112 | 113 | open inner class TextOnlyViewHolder(view: View) : RecyclerView.ViewHolder(view) { 114 | val heading: TextView = view.heading 115 | val subHeading: TextView 116 | 117 | init { 118 | subHeading = view.subHeading 119 | } 120 | } 121 | 122 | inner class CardViewHolder(view: View) : TextOnlyViewHolder(view) { 123 | fun cancelAnimations() { 124 | translateTextAnim?.cancel() 125 | heading.translationY = 0f 126 | subHeading.translationY = 0f 127 | } 128 | 129 | fun setUpForTextAnim() { 130 | heading.translationY = 10000f 131 | subHeading.translationY = 10000f 132 | } 133 | 134 | val image: LoaderImageView = view.image 135 | var translateTextAnim: ValueAnimator? = null 136 | 137 | } 138 | } -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/UnsplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.content.Context 5 | import com.squareup.picasso.LruCache 6 | import com.squareup.picasso.Picasso 7 | 8 | class UnsplashViewModel : ViewModel() { 9 | lateinit var picasso: Picasso 10 | 11 | fun createPicasso(context: Context) { 12 | picasso = Picasso.Builder(context.applicationContext).memoryCache(picassoCache).build() 13 | } 14 | 15 | val picassoCache = LruCache(100000000) 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/animation/CardTransitionHelper.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind.animation 2 | 3 | import android.animation.ObjectAnimator 4 | import android.animation.PointFEvaluator 5 | import android.animation.ValueAnimator 6 | import android.app.Activity 7 | import android.graphics.Path 8 | import android.graphics.PathMeasure 9 | import android.graphics.PointF 10 | import android.graphics.Rect 11 | import android.os.SystemClock 12 | import android.support.constraint.ConstraintLayout 13 | import android.support.constraint.ConstraintSet 14 | import android.support.v7.widget.CardView 15 | import android.view.View 16 | import android.view.ViewGroup 17 | import android.view.ViewTreeObserver 18 | import android.view.animation.LinearInterpolator 19 | import android.view.animation.PathInterpolator 20 | import android.widget.FrameLayout 21 | import kotlinx.android.synthetic.main.card2.view.* 22 | import no.danielzeller.blurbehind.* 23 | import no.danielzeller.blurbehind.extensions.* 24 | import no.danielzeller.blurbehindlib.BlurBehindLayout 25 | 26 | private const val FADE_TEXT_DURATION = 300L 27 | const val FADE_BARS_DURATION = 100L 28 | const val MOVE_DURATION = 700L 29 | const val TARGET_BLUR_RADIUS = 60f 30 | const val BACKGROUND_VIEWS_SCALED_DOWN_SIZE = 0.85f 31 | 32 | val moveInterpolator = PathInterpolator(.52f, 0f, .18f, 1f) 33 | val scaleInterpolator = PathInterpolator(.24f, 0f, .13f, 1f) 34 | 35 | class FrameRateCounter { 36 | private var lastTime: Long = 0 37 | 38 | fun timeStep(): Float { 39 | val time = SystemClock.uptimeMillis() 40 | val timeDelta = time - lastTime 41 | val timeDeltaSeconds = if (lastTime > 0.0f) timeDelta / 1000.0f else 0.0f 42 | lastTime = time 43 | return Math.min(0.015f, timeDeltaSeconds) 44 | } 45 | } 46 | 47 | class CardTransitionHelper(private val cardRootView: ConstraintLayout, private val backgroundView: ViewGroup, val textContainer: View) { 48 | 49 | private val constraintSet = ConstraintSet() 50 | private val cardViewCenterPosition = floatArrayOf(0f, 0f) 51 | private val cardView: CardView = cardRootView.getChildAt(0) as CardView 52 | private lateinit var movePath: Path 53 | private val originSize = PointF() 54 | private val targetSize = PointF() 55 | private var currentPathMoveProgress = 0f 56 | private val runningAnimations = ArrayList() 57 | private val frameRateCounter = FrameRateCounter() 58 | private var isEnterAnimating = false 59 | 60 | fun animateCardIn() { 61 | cardRootView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { 62 | override fun onGlobalLayout() { 63 | if (cardRootView.image.drawable != null) { 64 | 65 | val drawable = cardRootView.image.drawable 66 | val targetWidth = backgroundView.width.toFloat() 67 | val targetHeight = Math.min(targetWidth, (drawable.intrinsicHeight.toFloat() / drawable.intrinsicWidth.toFloat()) * targetWidth) 68 | 69 | targetSize.set(targetWidth, targetHeight) 70 | originSize.set(cardRootView.width.toFloat(), cardRootView.height.toFloat()) 71 | 72 | movePath = createMovePath() 73 | animateCardPosition(1f) 74 | animateCardSize(targetSize) 75 | animateCardCornerRadius(0f) 76 | scaleBackgroundView(BACKGROUND_VIEWS_SCALED_DOWN_SIZE, 0.5f, scaleInterpolator, -1.65f) 77 | fadeCardViewTextViews(0.0f, 0) 78 | animateTextContainer() 79 | isEnterAnimating = true 80 | cardRootView.viewTreeObserver.removeOnGlobalLayoutListener(this) 81 | } 82 | } 83 | }) 84 | } 85 | 86 | private var textContainerOffset = 0f 87 | private fun animateTextContainer() { 88 | ObjectAnimator.ofFloat(textContainer, View.ALPHA, textContainer.alpha, 1f).setDuration((MOVE_DURATION *0.6f).toLong()).delay((MOVE_DURATION *0.4f).toLong()).interpolate(moveInterpolator).start(runningAnimations) 89 | ValueAnimator.ofFloat(-textContainer.height.toFloat()/2f, 0f).setDuration(MOVE_DURATION).interpolate(moveInterpolator).onUpdate { anim -> textContainerOffset = anim as Float }.start(runningAnimations) 90 | } 91 | 92 | fun animateCardOut() { 93 | cancelAllRunningAnimations() 94 | animateCardPosition(0f) 95 | animateCardSize(originSize) 96 | animateCardCornerRadius(cardRootView.resources.getDimension(R.dimen.card_view_corner_radius)) 97 | fadeCardViewTextViews(1.0f, MOVE_DURATION - FADE_TEXT_DURATION) 98 | scaleBackgroundView(1f, 0.3f, scaleInterpolator, -0.9f) 99 | ObjectAnimator.ofFloat(textContainer, View.ALPHA, textContainer.alpha, 0f).setDuration((MOVE_DURATION * 0.4f).toLong()).start(runningAnimations) 100 | } 101 | 102 | fun cancelAllRunningAnimations() { 103 | for (anim in runningAnimations) { 104 | anim.removeAllListeners() 105 | anim.cancel() 106 | } 107 | runningAnimations.clear() 108 | } 109 | 110 | fun fadeInBlur(blurView: BlurBehindLayout, blurDimmer: View) { 111 | ValueAnimator.ofFloat(0f, TARGET_BLUR_RADIUS).setDuration(MOVE_DURATION - FADE_BARS_DURATION) 112 | .interpolate(scaleInterpolator).delay(FADE_BARS_DURATION).onUpdate { value -> 113 | blurView.blurRadius = value as Float 114 | }.onEnd { isEnterAnimating = false }.start(runningAnimations) 115 | 116 | ObjectAnimator.ofFloat(blurDimmer, View.ALPHA, 0f, 1f).setDuration((MOVE_DURATION * 1.2f).toLong()) 117 | .interpolate(moveInterpolator).delay(FADE_BARS_DURATION).start(runningAnimations) 118 | } 119 | 120 | fun fadeOutFullscreenBlur(blurView: BlurBehindLayout, blurDimmer: View) { 121 | blurView.enable() 122 | blurView.updateForMilliSeconds(MOVE_DURATION) 123 | val blurMultiplier = if (isEnterAnimating) 1f else 2f 124 | ValueAnimator.ofFloat(blurView.blurRadius * blurMultiplier, 0f).setDuration((MOVE_DURATION * 0.9f).toLong()) 125 | .interpolate(LinearInterpolator()).onUpdate { value -> 126 | blurView.blurRadius = value as Float 127 | }.onEnd { 128 | blurView.disable() 129 | }.start(runningAnimations) 130 | 131 | ObjectAnimator.ofFloat(blurDimmer, View.ALPHA, blurDimmer.alpha, 0f).setDuration(MOVE_DURATION - FADE_BARS_DURATION) 132 | .interpolate(moveInterpolator).start(runningAnimations) 133 | } 134 | 135 | fun fadeOutTopAndBottomBlurViews(activity: Activity?) { 136 | val appBarDimmer = activity?.findViewById(R.id.appBarFullDimmer) 137 | val navBarDimmer = activity?.findViewById(R.id.navigationBarFullDimmer) 138 | appBarDimmer?.visibility = View.VISIBLE 139 | navBarDimmer?.visibility = View.VISIBLE 140 | val appBarBlur = activity?.findViewById(R.id.appBarBlurLayout) 141 | val navBarBlur = activity?.findViewById(R.id.navigationBarBlurLayout) 142 | appBarBlur?.updateForMilliSeconds(MOVE_DURATION) 143 | navBarBlur?.updateForMilliSeconds(MOVE_DURATION) 144 | 145 | ObjectAnimator.ofFloat(appBarDimmer, View.ALPHA, 0f, 1f).setDuration(MOVE_DURATION).start(runningAnimations) 146 | ObjectAnimator.ofFloat(navBarDimmer, View.ALPHA, 0f, 1f).setDuration(MOVE_DURATION).onEnd { 147 | appBarBlur?.disable() 148 | navBarBlur?.disable() 149 | }.start(runningAnimations) 150 | } 151 | 152 | fun fadeInTopAndBottomBlurViews(activity: Activity?, onExitAnimationComplete: () -> Unit) { 153 | val appBarDimmer = activity?.findViewById(R.id.appBarFullDimmer)!! 154 | val navBarDimmer = activity.findViewById(R.id.navigationBarFullDimmer) 155 | val appBarBlur = activity.findViewById(R.id.appBarBlurLayout) 156 | val navBarBlur = activity.findViewById(R.id.navigationBarBlurLayout) 157 | appBarBlur?.enable() 158 | navBarBlur?.enable() 159 | appBarBlur?.updateForMilliSeconds(MOVE_DURATION) 160 | navBarBlur?.updateForMilliSeconds(MOVE_DURATION) 161 | ObjectAnimator.ofFloat(appBarDimmer, View.ALPHA, appBarDimmer.alpha, 0f).setDuration(MOVE_DURATION).start(runningAnimations) 162 | ObjectAnimator.ofFloat(navBarDimmer, View.ALPHA, appBarDimmer.alpha, 0f).setDuration(MOVE_DURATION).onEnd { 163 | navBarDimmer?.visibility = View.GONE 164 | appBarDimmer.visibility = View.GONE 165 | onExitAnimationComplete.invoke() 166 | }.start(runningAnimations) 167 | } 168 | 169 | private fun fadeCardViewTextViews(toAlpha: Float, delay: Long) { 170 | ObjectAnimator.ofFloat(cardView.heading, View.ALPHA, cardView.heading.alpha, toAlpha).setDuration(FADE_TEXT_DURATION).delay(delay).start(runningAnimations) 171 | if (cardView.subHeading != null) 172 | ObjectAnimator.ofFloat(cardView.subHeading, View.ALPHA, cardView.subHeading.alpha, toAlpha).setDuration(FADE_TEXT_DURATION).delay(delay).start(runningAnimations) 173 | 174 | } 175 | 176 | private fun animateCardPosition(toPosition: Float) { 177 | 178 | val pm = PathMeasure(movePath, false) 179 | ValueAnimator.ofFloat(currentPathMoveProgress, toPosition).setDuration(MOVE_DURATION).interpolate(moveInterpolator).onUpdate { anim -> 180 | currentPathMoveProgress = anim as Float 181 | pm.getPosTan(pm.length * currentPathMoveProgress, cardViewCenterPosition, null) 182 | 183 | }.start(runningAnimations) 184 | } 185 | 186 | private fun createMovePath(): Path { 187 | val movePath = Path() 188 | val cardViewPos = Rect() 189 | val centerX = targetSize.x / 2f 190 | val centerY = targetSize.y / 2f + cardRootView.resources.getDimension(R.dimen.top_bar_height) 191 | cardRootView.getHitRect(cardViewPos) 192 | movePath.moveTo(cardViewPos.exactCenterX(), cardViewPos.exactCenterY()) 193 | movePath.cubicTo(centerX + (centerX - cardViewPos.exactCenterX()) * 0.5f, cardViewPos.exactCenterY() - (cardViewPos.exactCenterY() - centerY) / 4, 194 | centerX, cardViewPos.exactCenterY() + (centerY - cardViewPos.exactCenterY()) / 2f, 195 | centerX, centerY) 196 | return movePath 197 | } 198 | 199 | private fun animateCardCornerRadius(toRadius: Float) { 200 | ValueAnimator.ofFloat(cardView.radius, toRadius).setDuration(MOVE_DURATION / 2).delay(MOVE_DURATION / 2) 201 | .onUpdate { value -> 202 | val cardRadius = value as Float 203 | cardView.radius = cardRadius 204 | }.start(runningAnimations) 205 | } 206 | 207 | private fun animateCardSize(targetSize: PointF) { 208 | ValueAnimator.ofObject(PointFEvaluator(), PointF(cardRootView.width.toFloat(), cardRootView.height.toFloat()), targetSize) 209 | .setDuration(MOVE_DURATION).interpolate(moveInterpolator).onUpdate { value -> 210 | val size = value as PointF 211 | constraintSet.clone(cardRootView) 212 | constraintSet.setDimensionRatio(cardView.id, "1:" + (size.y / size.x)) 213 | constraintSet.applyTo(cardRootView) 214 | 215 | val params = cardRootView.layoutParams as FrameLayout.LayoutParams 216 | params.leftMargin = (cardViewCenterPosition[0] - size.x / 2).toInt() 217 | params.topMargin = (cardViewCenterPosition[1] - size.y / 2).toInt() 218 | params.width = size.x.toInt() 219 | params.height = size.y.toInt() 220 | cardRootView.layoutParams = params 221 | textContainer.translationY = textContainerOffset + cardRootView.bottom 222 | }.start(runningAnimations) 223 | } 224 | 225 | private fun scaleBackgroundView(toSize: Float, pivotY: Float, scaleInterpolator: PathInterpolator, rotateAmount: Float) { 226 | var easedScale = (backgroundView.scaleX - BACKGROUND_VIEWS_SCALED_DOWN_SIZE) * 200f 227 | var easedScaleOffset = 0f 228 | var disableFlipAnimation = false 229 | frameRateCounter.timeStep() 230 | 231 | if (!isEnterAnimating) { 232 | backgroundView.pivotY = backgroundView.height * pivotY 233 | } else { 234 | disableFlipAnimation = true 235 | ObjectAnimator.ofFloat(backgroundView, View.ROTATION_X, backgroundView.rotationX, 0f).setDuration(MOVE_DURATION - FADE_BARS_DURATION) 236 | .delay(FADE_BARS_DURATION).interpolate(moveInterpolator).start(runningAnimations) 237 | } 238 | ValueAnimator.ofFloat(backgroundView.scaleX, toSize).setDuration(MOVE_DURATION - FADE_BARS_DURATION) 239 | .delay(FADE_BARS_DURATION).interpolate(scaleInterpolator).onUpdate { anim -> 240 | val scale = anim as Float 241 | backgroundView.scaleX = scale 242 | backgroundView.scaleY = scale 243 | 244 | if (!disableFlipAnimation) { 245 | //Little trick to give the impression ov some air resistance making the view flip slightly :) 246 | val targetScaleForEasedRotation = (scale - BACKGROUND_VIEWS_SCALED_DOWN_SIZE) * 200f 247 | val time = frameRateCounter.timeStep() 248 | val easeAmount = ((targetScaleForEasedRotation - easedScale) * time) * 20f 249 | easedScaleOffset += (easeAmount - easedScaleOffset) * time * 25f 250 | backgroundView.rotationX = easedScaleOffset * rotateAmount 251 | easedScale += easeAmount 252 | } 253 | 254 | }.start(runningAnimations) 255 | } 256 | } -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/animation/LoaderImageView.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind.animation 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.graphics.* 6 | import android.support.v7.widget.AppCompatImageView 7 | import android.util.AttributeSet 8 | import android.view.animation.PathInterpolator 9 | import no.danielzeller.blurbehind.R 10 | import no.danielzeller.blurbehind.extensions.interpolate 11 | import no.danielzeller.blurbehind.extensions.onEnd 12 | import no.danielzeller.blurbehind.extensions.onUpdate 13 | 14 | const val CLIP_ANIM_DURATION = 800L 15 | private const val ROTATE_SPEED = 0.3f 16 | val scaleProgressBarInterpolator = PathInterpolator(.63f, .3f, 0f, .99f) 17 | 18 | class LoaderImageView(context: Context?, attrs: AttributeSet?) : AppCompatImageView(context, attrs) { 19 | 20 | private var introAnim: ValueAnimator? = null 21 | private var scale = 1f 22 | 23 | var isLoaderVisible = false 24 | set(value) { 25 | field = value 26 | invalidate() 27 | } 28 | 29 | private val paint = Paint() 30 | private val color1: Int 31 | private val color2: Int 32 | private val color3: Int 33 | private val dotSize: Float 34 | 35 | private val circlePath1 = Path() 36 | private val circlePath2 = Path() 37 | private val circlePath3 = Path() 38 | private val clipPath = Path() 39 | 40 | private lateinit var pm1: PathMeasure 41 | private lateinit var pm2: PathMeasure 42 | private lateinit var pm3: PathMeasure 43 | 44 | private var clipPathScale = 1f 45 | private var rotateAmount = 0f 46 | 47 | private val fc = FrameRateCounter() 48 | private val pathPosition = floatArrayOf(0f, 0f) 49 | 50 | init { 51 | paint.isAntiAlias = true 52 | color1 = resources.getColor(R.color.progressColor1, null) 53 | color2 = resources.getColor(R.color.progressColor2, null) 54 | color3 = resources.getColor(R.color.progressColor3, null) 55 | paint.strokeWidth = resources.getDimension(R.dimen.progressCircleStrokeWidth) 56 | dotSize = resources.getDimension(R.dimen.progressDotRadius) 57 | } 58 | 59 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 60 | super.onLayout(changed, left, top, right, bottom) 61 | buildCirclePaths() 62 | } 63 | 64 | private fun buildCirclePaths() { 65 | circlePath1.reset() 66 | circlePath2.reset() 67 | circlePath3.reset() 68 | circlePath1.addCircle(width / 2f, height / 2f, resources.getDimension(R.dimen.progressCircleRadius1), Path.Direction.CW) 69 | circlePath2.addCircle(width / 2f, height / 2f, resources.getDimension(R.dimen.progressCircleRadius2), Path.Direction.CW) 70 | circlePath3.addCircle(width / 2f, height / 2f, resources.getDimension(R.dimen.progressCircleRadius3), Path.Direction.CW) 71 | pm1 = PathMeasure(circlePath1, true) 72 | pm2 = PathMeasure(circlePath2, true) 73 | pm3 = PathMeasure(circlePath3, true) 74 | } 75 | 76 | fun introAnimate() { 77 | introAnim = ValueAnimator.ofFloat(1f, 0f).setDuration(CLIP_ANIM_DURATION).onUpdate { value -> 78 | val animatedValue = value as Float 79 | scale = 1f + animatedValue 80 | clipPathScale = animatedValue 81 | }.interpolate(scaleProgressBarInterpolator).onEnd { isLoaderVisible = false } 82 | introAnim!!.start() 83 | } 84 | 85 | override fun onDraw(canvas: Canvas) { 86 | val count = canvas.save() 87 | canvas.scale(scale, scale, (width / 2).toFloat(), (height / 2).toFloat()) 88 | super.onDraw(canvas) 89 | canvas.restoreToCount(count) 90 | 91 | drawLoader(canvas) 92 | } 93 | 94 | private fun drawLoader(canvas: Canvas) { 95 | if (isLoaderVisible) { 96 | val count = canvas.save() 97 | 98 | clipLoader(canvas) 99 | increaseRotation() 100 | 101 | canvas.drawColor(Color.BLACK) 102 | drawCircles(canvas) 103 | 104 | canvas.restoreToCount(count) 105 | 106 | invalidate() 107 | } 108 | } 109 | 110 | private fun drawCircles(canvas: Canvas) { 111 | canvas.rotate(-90f, width / 2f, height / 2f) 112 | val clipScaleParallax = (1f - clipPathScale) * 0.4f 113 | canvas.scale(clipPathScale + clipScaleParallax, clipPathScale + clipScaleParallax, width / 2f, height / 2f) 114 | drawPath(canvas, circlePath1, color1, rotateAmount * 3f, pm1) 115 | drawPath(canvas, circlePath2, color2, rotateAmount * 2f, pm2) 116 | drawPath(canvas, circlePath3, color3, rotateAmount, pm3) 117 | } 118 | 119 | private fun increaseRotation() { 120 | rotateAmount += fc.timeStep() * ROTATE_SPEED 121 | if (rotateAmount > 1f) rotateAmount = 0f 122 | } 123 | 124 | private fun clipLoader(canvas: Canvas) { 125 | if (clipPathScale != 1f) { 126 | clipPath.reset() 127 | clipPath.addCircle(width / 2f, height / 2f, width * 0.75f * clipPathScale, Path.Direction.CW) 128 | canvas.clipPath(clipPath) 129 | } 130 | } 131 | 132 | private fun drawPath(canvas: Canvas, path: Path, color: Int, rotationAmount: Float, pm: PathMeasure) { 133 | paint.style = Paint.Style.STROKE 134 | paint.color = color 135 | canvas.drawPath(path, paint) 136 | paint.style = Paint.Style.FILL 137 | 138 | pm.getPosTan(pm.length * (rotationAmount % 1f), pathPosition, null) 139 | canvas.drawCircle(pathPosition[0], pathPosition[1], dotSize, paint) 140 | } 141 | 142 | fun cancelIntroAnim() { 143 | introAnim?.cancel() 144 | scale = 1f 145 | clipPathScale = 1f 146 | } 147 | } -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/extensions/extensions.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind.extensions 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorListenerAdapter 5 | import android.animation.TimeInterpolator 6 | import android.animation.ValueAnimator 7 | 8 | inline fun ValueAnimator.onEnd(crossinline func: () -> Unit): ValueAnimator { 9 | addListener(object : AnimatorListenerAdapter() { 10 | override fun onAnimationEnd(animation: Animator?) { 11 | super.onAnimationEnd(animation) 12 | func() 13 | } 14 | }) 15 | return this 16 | } 17 | 18 | inline fun ValueAnimator.onStart(crossinline func: () -> Unit): ValueAnimator { 19 | addListener(object : AnimatorListenerAdapter() { 20 | override fun onAnimationStart(animation: Animator?) { 21 | super.onAnimationStart(animation) 22 | func() 23 | } 24 | }) 25 | return this 26 | } 27 | 28 | inline fun ValueAnimator.onUpdate(crossinline func: (value: Any) -> Unit): ValueAnimator { 29 | addUpdateListener { animation -> 30 | func(animation.animatedValue) 31 | } 32 | return this 33 | } 34 | 35 | inline fun ValueAnimator.delay(delay: Long): ValueAnimator { 36 | startDelay = delay 37 | return this 38 | } 39 | 40 | inline fun ValueAnimator.start(runningAnims: ArrayList) { 41 | runningAnims.add(this) 42 | start() 43 | } 44 | 45 | inline fun ValueAnimator.interpolate(interp: TimeInterpolator): ValueAnimator { 46 | interpolator = interp 47 | return this 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/fragments/DetailsFragment.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind.fragments 2 | 3 | import android.arch.lifecycle.ViewModelProviders 4 | import android.graphics.* 5 | import android.os.Bundle 6 | import android.support.constraint.ConstraintLayout 7 | import android.support.v4.app.Fragment 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import android.widget.FrameLayout 12 | import kotlinx.android.synthetic.main.card2.view.* 13 | import kotlinx.android.synthetic.main.fragment_details.view.* 14 | import no.danielzeller.blurbehind.model.UnsplashItem 15 | import kotlinx.android.synthetic.main.activity_main.view.* 16 | import no.danielzeller.blurbehind.R 17 | import no.danielzeller.blurbehind.UnsplashViewModel 18 | import no.danielzeller.blurbehind.animation.CardTransitionHelper 19 | import no.danielzeller.blurbehind.animation.MOVE_DURATION 20 | import no.danielzeller.blurbehindlib.ScreenRectUtil 21 | 22 | 23 | const val ORIGIN_VIEW_TAG = "origin_view_tag" 24 | private const val ORIGIN_VIEW_SCREEN_POSITION_KEY = "screen_pos" 25 | private const val UNSPLASH_ITEM_KEY = "unsplash_item" 26 | 27 | class DetailsFragment : Fragment() { 28 | 29 | var isExitAnimating = false 30 | private lateinit var unsplashItem: UnsplashItem 31 | 32 | private var originViewScreenPos = Rect() 33 | private lateinit var cardTransitionHelper: CardTransitionHelper 34 | 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | super.onCreate(savedInstanceState) 37 | arguments?.let { 38 | val originViewScreenPosArray = it.getIntegerArrayList(ORIGIN_VIEW_SCREEN_POSITION_KEY) 39 | unsplashItem = it.getSerializable(UNSPLASH_ITEM_KEY) as UnsplashItem 40 | if (originViewScreenPosArray != null) { 41 | originViewScreenPos.set(originViewScreenPosArray[0], originViewScreenPosArray[1], originViewScreenPosArray[2], originViewScreenPosArray[3]) 42 | } 43 | } 44 | } 45 | 46 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 47 | val rootView = inflater.inflate(R.layout.fragment_details, container, false) 48 | val viewToBlur = activity?.findViewById(R.id.viewToBlur) as ViewGroup 49 | 50 | val cardViewRoot = createCardView(rootView as FrameLayout, inflater) 51 | setupBlurViews(rootView, viewToBlur) 52 | cardTransitionHelper = CardTransitionHelper(cardViewRoot, viewToBlur.recyclerView, rootView.textContainer) 53 | cardTransitionHelper.animateCardIn() 54 | cardTransitionHelper.fadeInBlur(rootView.fullscreenBlur, rootView.fullscreenDimmer) 55 | cardTransitionHelper.fadeOutTopAndBottomBlurViews(activity) 56 | return rootView 57 | } 58 | 59 | private fun createCardView(rootView: FrameLayout, inflater: LayoutInflater): ConstraintLayout { 60 | val cardRootView = inflater.inflate(unsplashItem.layoutID, rootView, false) as ConstraintLayout 61 | val layoutParams = FrameLayout.LayoutParams(originViewScreenPos.right - originViewScreenPos.left, originViewScreenPos.bottom - originViewScreenPos.top) 62 | layoutParams.leftMargin = originViewScreenPos.left 63 | layoutParams.topMargin = originViewScreenPos.top 64 | rootView.addView(cardRootView, layoutParams) 65 | cardRootView.heading.text = unsplashItem.heading 66 | cardRootView.subHeading?.text = unsplashItem.subHeading 67 | rootView.detailsArticleContent.text = unsplashItem.articleContent 68 | rootView.detailsHeading.text = unsplashItem.heading 69 | getBitmap(cardRootView) 70 | 71 | return cardRootView 72 | } 73 | 74 | private fun getBitmap(cardRootView: ConstraintLayout) { 75 | val model = activity?.run { 76 | ViewModelProviders.of(this).get(UnsplashViewModel::class.java) 77 | } ?: throw Exception("Invalid Activity") 78 | 79 | val picassoCache = model.picassoCache 80 | val bitmap = picassoCache.get(unsplashItem.imageUrl + "\n") 81 | if (bitmap != null) { 82 | cardRootView.image.setImageBitmap(bitmap) 83 | } else { 84 | model.picasso.load(unsplashItem.imageUrl).fit().into(cardRootView.image) 85 | } 86 | } 87 | 88 | private fun setupBlurViews(rootView: View, viewToBlur: ViewGroup) { 89 | rootView.fullscreenBlur.viewBehind = viewToBlur 90 | rootView.fullscreenBlur.updateForMilliSeconds(MOVE_DURATION) 91 | } 92 | 93 | private fun onExitAnimationComplete() { 94 | val viewToBlur = activity?.findViewById(R.id.viewToBlur) as ViewGroup 95 | val originView = viewToBlur.findViewWithTag(ORIGIN_VIEW_TAG) 96 | originView?.visibility = View.VISIBLE 97 | originView?.tag = null 98 | fragmentManager?.beginTransaction()?.remove(this)?.commit() 99 | } 100 | 101 | fun exitAnimateAndRemove() { 102 | cardTransitionHelper.animateCardOut() 103 | cardTransitionHelper.fadeOutFullscreenBlur(view!!.fullscreenBlur, view!!.fullscreenDimmer) 104 | cardTransitionHelper.fadeInTopAndBottomBlurViews(activity) { onExitAnimationComplete() } 105 | isExitAnimating = true 106 | } 107 | 108 | fun cancelAllRunningAnimations() { 109 | cardTransitionHelper.cancelAllRunningAnimations() 110 | } 111 | 112 | companion object { 113 | 114 | @JvmStatic 115 | fun newInstance(clickedView: View, unsplashItem: UnsplashItem) = 116 | DetailsFragment().apply { 117 | val rect = ScreenRectUtil.getScreenRect(clickedView) 118 | arguments = Bundle().apply { 119 | putIntegerArrayList(ORIGIN_VIEW_SCREEN_POSITION_KEY, arrayListOf(rect.left, rect.top, rect.right, rect.bottom)) 120 | putSerializable(UNSPLASH_ITEM_KEY, unsplashItem) 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/fragments/DialogFragment.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind.fragments 2 | 3 | 4 | import android.animation.ObjectAnimator 5 | import android.animation.ValueAnimator 6 | import android.os.Bundle 7 | import android.support.v4.app.Fragment 8 | import android.view.* 9 | import android.view.animation.PathInterpolator 10 | import kotlinx.android.synthetic.main.fragment_dialog.view.* 11 | import no.danielzeller.blurbehind.R 12 | import no.danielzeller.blurbehind.animation.* 13 | import no.danielzeller.blurbehind.extensions.delay 14 | import no.danielzeller.blurbehind.extensions.interpolate 15 | import no.danielzeller.blurbehind.extensions.onEnd 16 | import no.danielzeller.blurbehind.extensions.onUpdate 17 | import no.danielzeller.blurbehindlib.BlurBehindLayout 18 | 19 | const val SCALED_DOWN_SIZE = 0.7f 20 | const val ANIM_DURATION = 400L 21 | 22 | class DialogFragment : Fragment(), View.OnTouchListener { 23 | 24 | private lateinit var blurViewContent: View 25 | private lateinit var blurView: BlurBehindLayout 26 | private val frameRateCounter = FrameRateCounter() 27 | private var isExitAnimating = false 28 | 29 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 30 | savedInstanceState: Bundle?): View? { 31 | 32 | val rootView = inflater.inflate(R.layout.fragment_dialog, container, false) 33 | rootView.setOnTouchListener(this) 34 | setupBlurView(rootView) 35 | enterAnimate(rootView) 36 | return rootView 37 | } 38 | 39 | override fun onTouch(v: View?, event: MotionEvent?): Boolean { 40 | exitAnimateAndRemove() 41 | return false 42 | } 43 | 44 | private fun enterAnimate(rootView: View) { 45 | blurViewContent = rootView.blurViewFirstChild 46 | blurViewContent.scaleX = SCALED_DOWN_SIZE 47 | blurViewContent.scaleY = SCALED_DOWN_SIZE 48 | rootView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { 49 | override fun onGlobalLayout() { 50 | 51 | scaleBackgroundView(1f, 0.3f, scaleInterpolator, -1.2f) 52 | rootView.viewTreeObserver.removeOnGlobalLayoutListener(this) 53 | ObjectAnimator.ofFloat(blurView, View.ALPHA, 0f, 1f).setDuration(ANIM_DURATION / 2).delay(50).start() 54 | blurView.updateForMilliSeconds(ANIM_DURATION) 55 | } 56 | }) 57 | } 58 | 59 | private fun setupBlurView(rootView: View) { 60 | blurView = rootView.dialogBlurView 61 | blurView.viewBehind = activity?.findViewById(R.id.viewToBlur) 62 | blurView.alpha = 0f 63 | } 64 | 65 | fun exitAnimateAndRemove() { 66 | if (!isExitAnimating) { 67 | isExitAnimating = true 68 | blurView.updateForMilliSeconds(ANIM_DURATION) 69 | scaleBackgroundView(SCALED_DOWN_SIZE, 0.5f, scaleInterpolator, -1.5f) 70 | ObjectAnimator.ofFloat(blurView, View.ALPHA, 1f, 0f).setDuration(ANIM_DURATION).onEnd { 71 | fragmentManager?.beginTransaction()?.remove(this)?.commit() 72 | }.start() 73 | } 74 | } 75 | 76 | private fun scaleBackgroundView(toSize: Float, pivotY: Float, scaleInterpolator: PathInterpolator, rotateAmount: Float) { 77 | 78 | var easedScale = (blurViewContent.scaleX - SCALED_DOWN_SIZE) * 200f 79 | var easedScaleOffset = 0f 80 | frameRateCounter.timeStep() 81 | blurViewContent.pivotY = blurViewContent.height * pivotY 82 | 83 | ValueAnimator.ofFloat(blurViewContent.scaleX, toSize).setDuration(ANIM_DURATION) 84 | .interpolate(scaleInterpolator).onUpdate { anim -> 85 | val scale = anim as Float 86 | blurViewContent.scaleX = scale 87 | blurViewContent.scaleY = scale 88 | 89 | //Little trick to give the impression ov some air resistance making the view flip slightly :) 90 | val targetScaleForEasedRotation = (scale - SCALED_DOWN_SIZE) * 200f 91 | val time = frameRateCounter.timeStep() 92 | val easeAmount = ((targetScaleForEasedRotation - easedScale) * time) * 20f 93 | easedScaleOffset += (easeAmount - easedScaleOffset) * time * 30f 94 | blurViewContent.rotationX = easedScaleOffset * rotateAmount 95 | easedScale += easeAmount 96 | 97 | 98 | }.start() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/no/danielzeller/blurbehind/model/UnsplashItem.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind.model 2 | 3 | import java.io.Serializable 4 | 5 | class UnsplashItem(val imageUrl: String, val heading: String, val subHeading: String, val layoutID: Int, val articleContent: String, val clickUnitIndex: Int) : Serializable -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_author.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielzeller/Blur-LIB-Android/20b685fa0d37285124bdb17b009024ec69751965/app/src/main/res/drawable-xxhdpi/ic_author.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/black_gradient.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corners_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 23 | 24 | 25 | 33 | 34 | 35 | 36 | 46 | 47 | 53 | 54 | 61 | 62 | 72 | 73 | 74 | 84 | 85 | 92 | 93 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card1.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card2.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 26 | 27 | 38 | 39 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card3.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 22 | 27 | 28 | 33 | 34 | 43 | 44 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card4.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 26 | 27 | 39 | 40 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 21 | 22 | 28 | 29 | 30 | 38 | 39 | 45 | 46 | 60 | 61 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 25 | 26 | 36 | 37 | 38 | 48 | 49 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | @color/dark_grey 5 | @color/progressColor3 6 | 7 | #1A1E1F 8 | #FFFFFF 9 | 10 | #fec31e 11 | #ee7966 12 | #8dd3ce 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 85dp 4 | 48dp 5 | 112dp 6 | 76dp 7 | 16dp 8 | 17dp 9 | 10sp 10 | 24sp 11 | 14sp 12 | 12sp 13 | 24dp 14 | 0.8dp 15 | 3dp 16 | 35dp 17 | 25dp 18 | 15dp 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Blur Demo 3 | 4 | By Daniel Zeller on Oct 22, 2018, 6:00am EDT 5 | Dialog Heading 6 | Hi, I\'m a dialog. The BlurBehindLayout has set useChildAlphaAsMask=true so that the blurring is clipped to the alpha of the first child within the layout. 7 | 8 | 9 | Breakthrough in baldness treatment 10 | Germany is planning a gexit 11 | I\'m a heading 12 | Merkeler has a new hobby 13 | Show dialog 14 | 15 | Stump says geese are no longer welcome in the U.S 16 | Denmark is building a canal 17 | New York needs coffee 18 | Google is selling Android 19 | GitHub 20 | 21 | Murderer says detective ruined his reputation 22 | Well I\'m certainly a heading 23 | Me too 24 | Woouoo 25 | Say Hello 26 | 27 | 28 | 29 | SPECIAL REPORT 30 | BREAKING NEWS 31 | 32 | 33 | Shows a dialog where blurring is clipped to the ChildView Alpha channel. 34 | POLITICS 35 | WORLD 36 | 37 | 38 | Checkout some other fun project on my GitHub Page. 39 | LIFESTYLE 40 | BREAKING NEWS 41 | Me too 42 | Woouoo 43 | Come say hi to the dev over at http://danielzeller.no 44 | 45 | 46 | 47 | These days there seems to be even more of a swagger as Ronald Stump strides across the South Lawn to board his green-liveried helicopter, Marine One, with his very real hair flapping in the wind. Stump stated in a public press release that his sole reason for becoming the US president, was to fight baldness. 48 | \"I feel that every man should have hair, like me\" he therefore issued that 15% of the US national budget should be used on baldness research. \n\nRead more at: danielzeller.no 49 | The Germans, lovers of wienerschnitzel and Cola Weizen are planning a quick withdrawal from the EU. German chancellor Angelica Merkeler stated at a public press conference, that Germans are sick of the rest of the EU\'s low work morale. \"We Germans work at least 10 hours a day without any breaks, while other countries have siestas and lunch breaks.\" She makes a call to the rest of EU: \"Start working harder you lazy slobs!\"\n\nRead more at: danielzeller.no 50 | The heading was outraged that nobody had seen his content. \"I\'m a heading, why won\'t nobody read my content.\"\n\nFull story: danielzeller.no 51 | German chancellor Angelica Merkeler has a new hobby. She recently started flying drones in the Berlin central area. \"It\'s so fun to fly around peeking into peoples windows with a GoPro camera\" she says, while taking a sip of a cold bananen weizen beer.\n\nRead full story at: danielzeller.no 52 | 53 | 54 | President Ronald Stump says in a public press release, that all Geese shall be deported from the US. When he was a child a Goose once bit his thumb. \"It hurt for at least three weeks\" he says wile biting his lips. Besides curing baldness, geese seem to be his main focus at the moment. America is spending millions of dollars trying to figure out how they can prevent the geese from flying back into the country after deportation.\n\nRead more at: danielzeller.no 55 | Denmark is building a canal along the border to Germany. Denmark\'s chancellor Nils Holgerson says that they have been envious to other countries that are an island for a long time. \"With this canal we will finally be an island just like Iceland and Japan\" he says while eating a bolle.\n\nRead more at: danielzeller.no 56 | The coffee supplies in New York city have gone dry. New Yorkers have been drinking an average of 50 cups a day for the last 8 months. The reason for this is reportedly that Hipsters no longer exist, they have morphed into whipsters, whom without coffee will turn into regular humans.\n\nRead more at: danielzeller.no 57 | The Android developers have been heard. Google is selling Android. In a public press statement, the head of Android engineering Sundae Pistachio says that the developers on the Android team are sick of the old and boring Android code. It\'s just so much work having to deal with all that legacy code. The engineers would rather work on new projects than having to spend countless hours bug fixing issues related to software rendering.\n\nFull story: danielzeller.no 58 | 59 | 60 | No comment needed.\n\nRead more: danielzeller.no 61 | After being accused of not being a heading, the heading made a public headline stating that it was indeed a heading.\n\nRead more at: danielzeller.no 62 | This was also a heading.\n\nFull story: danielzeller.no 63 | Mhmmm. In publishing and graphic design, lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document without relying on meaningful content (also called greeking). Replacing the actual content with placeholder text allows designers to design the form of the content before the content itself has been produced.\n\nFull story at: danielzeller.no 64 | 65 | 66 | 67 | hair 68 | exit 69 | heading 70 | drone,flying 71 | 72 | geese 73 | canal 74 | coffee 75 | iphone,desk,mobile 76 | 77 | reputation 78 | headline,news 79 | heading,news 80 | news,headline 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | 17 | 18 | 24 | 25 | 29 | 32 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/test/java/no/danielzeller/blurbehind/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehind 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /blurbehindlib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /blurbehindlib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | 4 | apply plugin: 'com.github.dcendents.android-maven' 5 | apply plugin: 'com.jfrog.bintray' 6 | 7 | repositories { 8 | mavenCentral() 9 | jcenter() 10 | } 11 | 12 | android { 13 | compileSdkVersion 28 14 | 15 | defaultConfig { 16 | minSdkVersion 16 17 | targetSdkVersion 28 18 | versionCode 1 19 | versionName "1.0" 20 | 21 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 22 | 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | } 32 | 33 | tasks.withType(Javadoc) { 34 | enabled = false 35 | } 36 | 37 | dependencies { 38 | 39 | implementation fileTree(dir: 'libs', include: ['*.jar']) 40 | implementation 'com.android.support:appcompat-v7:28.0.0' 41 | testImplementation 'junit:junit:4.12' 42 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 43 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 44 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 45 | } 46 | 47 | ext { 48 | bintrayRepo = 'maven' 49 | bintrayName = 'blurbehindlib' 50 | 51 | publishedGroupId = 'no.danielzeller.blurbehindlib' 52 | libraryName = 'BlurBehindLib' 53 | artifact = 'blurbehindlib' 54 | 55 | libraryDescription = 'This is a Library fir blurring the background of a View.' 56 | 57 | siteUrl = 'https://github.com/danielzeller/Blur-LIB-Android' 58 | gitUrl = 'https://github.com/danielzeller/Blur-LIB-Android.git' 59 | 60 | libraryVersion = '1.0.0' 61 | 62 | developerId = 'danielzeller' 63 | developerName = 'Daniel Zeller' 64 | developerEmail = 'daniel@danielzeller.no' 65 | 66 | licenseName = 'The Apache Software License, Version 2.0' 67 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 68 | allLicenses = ["Apache-2.0"] 69 | } 70 | 71 | group = publishedGroupId 72 | version = libraryVersion 73 | 74 | install { 75 | repositories.mavenInstaller { 76 | pom.project { 77 | packaging 'aar' 78 | groupId publishedGroupId 79 | artifactId artifact 80 | 81 | name libraryName 82 | description libraryDescription 83 | url siteUrl 84 | 85 | licenses { 86 | license { 87 | name licenseName 88 | url licenseUrl 89 | } 90 | } 91 | developers { 92 | developer { 93 | id developerId 94 | name developerName 95 | email developerEmail 96 | } 97 | } 98 | scm { 99 | connection gitUrl 100 | developerConnection gitUrl 101 | url siteUrl 102 | } 103 | } 104 | } 105 | } 106 | 107 | task sourcesJar(type: Jar) { 108 | classifier = 'sources' 109 | from android.sourceSets.main.java.srcDirs 110 | } 111 | 112 | task javadoc(type: Javadoc) { 113 | source = android.sourceSets.main.java.srcDirs 114 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 115 | } 116 | 117 | task javadocJar(type: Jar, dependsOn: javadoc) { 118 | classifier = 'javadoc' 119 | from javadoc.destinationDir 120 | } 121 | 122 | artifacts { 123 | archives javadocJar 124 | archives sourcesJar 125 | } 126 | 127 | Properties properties = new Properties() 128 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 129 | 130 | bintray { 131 | user = properties.getProperty("bintray.user") 132 | key = properties.getProperty("bintray.apikey") 133 | 134 | configurations = ['archives'] 135 | pkg { 136 | repo = bintrayRepo 137 | name = bintrayName 138 | desc = libraryDescription 139 | websiteUrl = siteUrl 140 | vcsUrl = gitUrl 141 | licenses = allLicenses 142 | dryRun = false 143 | publish = true 144 | override = false 145 | publicDownloadNumbers = true 146 | version { 147 | desc = libraryDescription 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /blurbehindlib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /blurbehindlib/src/androidTest/java/no/danielzeller/blurbehindlib/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("no.danielzeller.blurbehindlib.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/BlurBehindLayout.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.graphics.PorterDuff 6 | import android.opengl.GLSurfaceView 7 | import android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY 8 | import android.util.AttributeSet 9 | import android.view.Choreographer 10 | import android.view.TextureView 11 | import android.view.View 12 | import android.view.ViewTreeObserver 13 | import android.widget.FrameLayout 14 | import no.danielzeller.blurbehindlib.renderers.CommonRenderer 15 | import no.danielzeller.blurbehindlib.renderers.GLSurfaceViewRenderer 16 | import no.danielzeller.blurbehindlib.renderers.TextureViewRenderer 17 | import android.view.ViewGroup 18 | import kotlin.IllegalStateException 19 | 20 | 21 | enum class UpdateMode { 22 | CONTINUOUSLY, ON_SCROLL, MANUALLY 23 | } 24 | 25 | class BlurBehindLayout : FrameLayout { 26 | 27 | 28 | /** 29 | * The View behind the BlurBehindLayout, that is to be Blurred. 30 | */ 31 | var viewBehind: View? = null 32 | set(value) { 33 | checkParent(value) 34 | field = value 35 | } 36 | 37 | 38 | /** 39 | * Set the update mode. When updateMode is 40 | * UpdateMode.CONTINUOUSLY, the renderer is called 41 | * repeatedly to re-render the scene. When updateMode 42 | * is UpdateMode.ON_SCROLL, the renderer only renders 43 | * when a View is Scrolled. 44 | * When updateMode is UpdateMode.MANUALLY, the renderer only renders when the surface is created 45 | * and when updateForMilliSeconds(..) is called manually. 46 | * 47 | * @param updateMode one of the UpdateMode enums 48 | * @see #UpdateMode 49 | */ 50 | var updateMode = UpdateMode.CONTINUOUSLY 51 | set(value) { 52 | viewTreeObserver.removeOnScrollChangedListener(onScrollChangesListener) 53 | if (value == UpdateMode.ON_SCROLL) { 54 | addOnScrollListener() 55 | } 56 | field = value 57 | } 58 | 59 | 60 | /** 61 | * The blur radius. Higher value will give a stronger blur. 0f = no blur. 62 | */ 63 | var blurRadius = 40f 64 | set(value) { 65 | 66 | commonRenderer?.blurRadius = value 67 | field = value 68 | } 69 | 70 | /** 71 | * If true the BlurBehindLayout will use the alpha value of the first child as clipping mask. 72 | * This can for instance be used to create blur behind TexViews or have rounded edges etc. 73 | * NOTE This will force useTextureView to be true in order to support Transparent rendering. 74 | */ 75 | var useChildAlphaAsMask = false 76 | set(value) { 77 | commonRenderer?.useChildAlphaAsMask = value 78 | field = value 79 | checkTextureView() 80 | } 81 | 82 | private val thisViewPosition = intArrayOf(0, 0) 83 | private val behindViewPosition = intArrayOf(0, 0) 84 | private var useTextureView = false 85 | private var commonRenderer: CommonRenderer? = null 86 | private var blurTextureScale = 0.4f 87 | private lateinit var textureViewRenderer: TextureViewRenderer 88 | private lateinit var renderView: View 89 | private var updateViewUntil = -1L 90 | private var isBlurDisabled = false 91 | private var paddingVertical = 0f 92 | private val onScrollChangesListener = ViewTreeObserver.OnScrollChangedListener { updateForMilliSeconds(200) } 93 | 94 | constructor(context: Context, useTextureView: Boolean, blurTextureScale: Float, paddingVertical: Float) : super(context) { 95 | this.blurTextureScale = blurTextureScale 96 | this.useTextureView = useTextureView 97 | this.paddingVertical = paddingVertical 98 | initView(context) 99 | } 100 | 101 | constructor(context: Context, useChildAlphaAsMask: Boolean) : super(context) { 102 | this.useChildAlphaAsMask = useChildAlphaAsMask 103 | initView(context) 104 | } 105 | 106 | constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) { 107 | loadAttributesFromXML(attributeSet) 108 | initView(context) 109 | } 110 | 111 | 112 | /** 113 | * This will udate the View for the given time in milliseconds. Useful for when updateMode 114 | * is UpdateMode.ON_SCOLL or UpdateMode.MANUALLY. This can be can be used if there is some 115 | * animation or update running in the background. 116 | * @param milliSeconds How long should the View update for. 117 | */ 118 | fun updateForMilliSeconds(milliSeconds: Long) { 119 | updateViewUntil = System.currentTimeMillis() + milliSeconds 120 | Choreographer.getInstance().removeFrameCallback(frameCallBack) 121 | Choreographer.getInstance().postFrameCallback(frameCallBack) 122 | } 123 | 124 | /** 125 | * Disables the blur View. Useful for when the BlurView is used in animations. 126 | * Setting Visibility = View.GONE on a SurfaceView will cause a black flicker in 127 | * some occasions. Using this method disables the blurView updates and hodes it from the 128 | * screen without any flicker issues. 129 | */ 130 | fun disable() { 131 | Choreographer.getInstance().removeFrameCallback(frameCallBack) 132 | viewTreeObserver.removeOnScrollChangedListener(onScrollChangesListener) 133 | //Setting visibility=GONE causes a black flicker since the SurfaceView rendering 134 | //and View rendering is'nt 1-1 synced. Setting translation off the screen removes the flicker. 135 | //The View is'nt updated anyways since we only do a render in the frameCallBack 136 | renderView.translationX = 100000f 137 | isBlurDisabled = true 138 | } 139 | 140 | /** 141 | * Enables the blur View again. Should only be called if disable() has been called first. 142 | */ 143 | fun enable() { 144 | if (isBlurDisabled) { 145 | if (updateMode == UpdateMode.ON_SCROLL) { 146 | addOnScrollListener() 147 | } 148 | renderView.translationX = 0f 149 | isBlurDisabled = false 150 | updateForMilliSeconds(10) 151 | } 152 | } 153 | 154 | private fun initView(context: Context) { 155 | commonRenderer = CommonRenderer(context, blurTextureScale, useChildAlphaAsMask, paddingVertical) 156 | commonRenderer!!.blurRadius = blurRadius 157 | if (useTextureView) { 158 | createTextureView(context) 159 | } else { 160 | createGLSurfaceView(context) 161 | } 162 | } 163 | 164 | override fun setAlpha(alpha: Float) { 165 | if (useChildAlphaAsMask) { 166 | /** 167 | * There is a bug in Android, that renders black background when alpha i 1f, 168 | * this horrible hack fixes that. 169 | */ 170 | super.setAlpha(Math.min(alpha, 0.99f)) 171 | } else { 172 | super.setAlpha(alpha) 173 | } 174 | } 175 | 176 | private fun loadAttributesFromXML(attrs: AttributeSet?) { 177 | 178 | val typedArray = context.theme.obtainStyledAttributes( 179 | attrs, 180 | R.styleable.Blur, 181 | 0, 0) 182 | try { 183 | useTextureView = typedArray.getBoolean(R.styleable.Blur_useTextureView, false) 184 | useChildAlphaAsMask = typedArray.getBoolean(R.styleable.Blur_useChildAlphaAsMask, false) 185 | updateMode = convertIntToEnum(typedArray.getInteger(R.styleable.Blur_updateMode, updateMode.ordinal)) 186 | blurRadius = typedArray.getFloat(R.styleable.Blur_blurRadius, blurRadius) 187 | blurTextureScale = typedArray.getFloat(R.styleable.Blur_blurTextureScale, blurTextureScale) 188 | paddingVertical = typedArray.getFloat(R.styleable.Blur_blurPaddingVertical, resources.getDimension(R.dimen.defaultVerticalPadding)) 189 | 190 | } finally { 191 | typedArray.recycle() 192 | } 193 | } 194 | 195 | 196 | private fun addOnScrollListener() { 197 | viewTreeObserver.addOnScrollChangedListener(onScrollChangesListener) 198 | } 199 | 200 | private fun createGLSurfaceView(context: Context) { 201 | val openGLRenderer = GLSurfaceViewRenderer() 202 | 203 | val glSurfaceView = GLSurfaceView(context) 204 | glSurfaceView.setEGLContextClientVersion(2) 205 | glSurfaceView.setZOrderMediaOverlay(true) 206 | glSurfaceView.setRenderer(openGLRenderer) 207 | glSurfaceView.renderMode = RENDERMODE_WHEN_DIRTY 208 | 209 | addView(glSurfaceView) 210 | openGLRenderer.commonRenderer = commonRenderer!! 211 | 212 | renderView = glSurfaceView 213 | } 214 | 215 | private fun createTextureView(context: Context) { 216 | textureViewRenderer = TextureViewRenderer(context) 217 | 218 | val textureView = TextureView(context) 219 | textureView.surfaceTextureListener = textureViewRenderer 220 | 221 | addView(textureView) 222 | textureViewRenderer.commonRenderer = commonRenderer!! 223 | renderView = textureView 224 | } 225 | 226 | private fun redrawBlurTexture() { 227 | if (commonRenderer!!.isCreated && renderView.visibility == View.VISIBLE) { 228 | renderBehindViewToTexture() 229 | renderChildViewToTexture() 230 | updateRenderView() 231 | } 232 | if (updateMode == UpdateMode.CONTINUOUSLY || System.currentTimeMillis() < updateViewUntil) { 233 | Choreographer.getInstance().postFrameCallback(frameCallBack) 234 | } 235 | } 236 | 237 | private fun updateRenderView() { 238 | if (useTextureView) { 239 | textureViewRenderer.update() 240 | } else { 241 | (renderView as GLSurfaceView).requestRender() 242 | } 243 | } 244 | 245 | private var frameCallBack = Choreographer.FrameCallback { 246 | redrawBlurTexture() 247 | } 248 | 249 | private fun renderBehindViewToTexture() { 250 | val commonRenderer = commonRenderer!! 251 | 252 | val glCanvas = commonRenderer.behindViewSurfaceTexture.beginDraw() 253 | 254 | glCanvas?.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) 255 | viewBehind?.getLocationInWindow(behindViewPosition) 256 | getLocationInWindow(thisViewPosition) 257 | 258 | glCanvas?.scale(commonRenderer.scale, commonRenderer.scale) 259 | glCanvas?.translate(0f, paddingVertical * 0.5f) 260 | val behindMatrix = viewBehind?.matrix 261 | behindMatrix?.postTranslate(behindViewPosition[0] - thisViewPosition[0].toFloat() - paddingLeft, behindViewPosition[1] - thisViewPosition[1].toFloat() - paddingTop) 262 | glCanvas?.concat(behindMatrix) 263 | 264 | viewBehind?.draw(glCanvas) 265 | 266 | commonRenderer.behindViewSurfaceTexture.endDraw(glCanvas) 267 | } 268 | 269 | private fun renderChildViewToTexture() { 270 | if (useChildAlphaAsMask) { 271 | if (childCount > 1) { 272 | val childView = getChildAt(1) 273 | /** 274 | * Since the openGL drawing is falling a couple of frames behind the regular UI drawing 275 | * we render the child in openGL instead of the regular UI drawcall, so that it is 276 | * synced with the openGL drawCommands. 277 | */ 278 | if (childView.visibility != INVISIBLE) { 279 | childView.visibility = INVISIBLE 280 | } 281 | val commonRenderer = commonRenderer!! 282 | val glCanvas = commonRenderer.childViewSurfaceTexture.beginDraw() 283 | 284 | glCanvas?.scale(1f, height.toFloat() / (height.toFloat() + paddingVertical)) 285 | glCanvas?.translate(childView.left.toFloat(), childView.top + paddingVertical * 0.5f) 286 | glCanvas?.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) 287 | glCanvas?.concat(childView.matrix) 288 | childView.draw(glCanvas) 289 | 290 | commonRenderer.childViewSurfaceTexture.endDraw(glCanvas) 291 | } 292 | } 293 | } 294 | 295 | private fun convertIntToEnum(id: Int): UpdateMode { 296 | for (f in UpdateMode.values()) { 297 | if (f.ordinal == id) return f 298 | } 299 | return UpdateMode.CONTINUOUSLY 300 | } 301 | 302 | override fun onDetachedFromWindow() { 303 | super.onDetachedFromWindow() 304 | Choreographer.getInstance().removeFrameCallback(frameCallBack) 305 | viewTreeObserver.removeOnScrollChangedListener(onScrollChangesListener) 306 | commonRenderer?.destroyResources() 307 | } 308 | 309 | override fun onAttachedToWindow() { 310 | super.onAttachedToWindow() 311 | updateForMilliSeconds(500) 312 | } 313 | 314 | 315 | private fun checkTextureView() { 316 | if (!useTextureView && useChildAlphaAsMask) 317 | throw IllegalStateException("useChildAlphaAsMask=true requires useTextureView=true") 318 | } 319 | 320 | private fun checkParent(value: View?) { 321 | if (value != null && value is ViewGroup) { 322 | recursiveLoopChildren(value) 323 | } 324 | } 325 | 326 | private fun recursiveLoopChildren(parent: ViewGroup) { 327 | for (i in 0 until parent.childCount) { 328 | val child = parent.getChildAt(i) 329 | if (child == this) 330 | throw IllegalStateException("Blur Lib: The blurbehind view cannot be a parent of the BlurBehindLayout") 331 | if (child is ViewGroup) { 332 | recursiveLoopChildren(child) 333 | } 334 | } 335 | } 336 | 337 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 338 | renderView.layoutParams = LayoutParams(0, 0); 339 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 340 | renderView.measure(widthMeasureSpec, heightMeasureSpec); 341 | } 342 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/ScreenRectUtil.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | 6 | class ScreenRectUtil { 7 | companion object { 8 | fun getScreenRect(view: View): Rect { 9 | val src = FloatArray(8) 10 | val dst = floatArrayOf(0f, 0f, view.width.toFloat(), 0f, 0f, view.height.toFloat(), view.width.toFloat(), view.height.toFloat()) 11 | val matrix = view.matrix 12 | 13 | matrix.mapPoints(src, dst) 14 | return Rect((src[0] + view.left).toInt(), (src[1] + view.top).toInt(), (src[6] + +view.left).toInt(), (src[7] + +view.top).toInt()) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/opengl/RenderTexture.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.opengl 2 | 3 | import android.opengl.GLES20 4 | import android.opengl.GLES20.* 5 | 6 | class RenderTexture { 7 | 8 | private var fboWidth = 512 9 | private var fboHeight = 512 10 | 11 | private var renderTextureID: Int = 0 12 | 13 | internal var fboTex: Int = 0 14 | private var renderBufferId: Int = 0 15 | private var internalFormat = GL_RGBA 16 | private var format = GL_RGBA 17 | 18 | fun initiateFrameBuffer(width: Int, height: Int): Int { 19 | 20 | fboWidth = width 21 | fboHeight = height 22 | val temp = IntArray(1) 23 | //generate fbo id 24 | GLES20.glGenFramebuffers(1, temp, 0) 25 | renderTextureID = temp[0] 26 | //generate texture 27 | GLES20.glGenTextures(1, temp, 0) 28 | fboTex = temp[0] 29 | //generate render buffer 30 | GLES20.glGenRenderbuffers(1, temp, 0) 31 | renderBufferId = temp[0] 32 | 33 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, renderTextureID) 34 | // 35 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTex) 36 | 37 | GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GLES20.GL_UNSIGNED_BYTE, null) 38 | 39 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT) 40 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT) 41 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR) 42 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR) 43 | 44 | GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderBufferId) 45 | GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height) 46 | 47 | GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, fboTex, 0) 48 | GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, renderBufferId) 49 | 50 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0) 51 | GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0) 52 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0) 53 | return renderTextureID 54 | } 55 | 56 | fun bindRenderTexture() { 57 | 58 | GLES20.glEnable(GLES20.GL_TEXTURE_2D) 59 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, renderTextureID) 60 | } 61 | 62 | fun unbindRenderTexture() { 63 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0) 64 | } 65 | 66 | fun deleteAllTextures() { 67 | GLES20.glDeleteBuffers(1, intArrayOf(renderTextureID), 0) 68 | GLES20.glDeleteRenderbuffers(1, intArrayOf(renderBufferId), 0) 69 | GLES20.glDeleteTextures(1, intArrayOf(fboTex), 0) 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/opengl/ShaderHelper.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.opengl 2 | 3 | import android.opengl.GLES20.* 4 | import android.util.Log 5 | 6 | object ShaderHelper { 7 | 8 | private const val TAG = "ShaderHelper" 9 | 10 | private fun compileVertexShader(shaderCode: String): Int { 11 | return compileShader(GL_VERTEX_SHADER, shaderCode) 12 | } 13 | 14 | private fun compileFragmentShader(shaderCode: String): Int { 15 | return compileShader(GL_FRAGMENT_SHADER, shaderCode) 16 | } 17 | 18 | private fun compileShader(type: Int, shaderCode: String): Int { 19 | 20 | val shaderObjectId = glCreateShader(type) 21 | 22 | if (shaderObjectId == 0) { 23 | 24 | Log.w(TAG, "Could not create new shader.") 25 | 26 | return 0 27 | } 28 | 29 | glShaderSource(shaderObjectId, shaderCode) 30 | 31 | glCompileShader(shaderObjectId) 32 | 33 | val compileStatus = IntArray(1) 34 | glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0) 35 | 36 | Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode 37 | + "\n:" + glGetShaderInfoLog(shaderObjectId)) 38 | 39 | // Verify the compile status. 40 | if (compileStatus[0] == 0) { 41 | // If it failed, delete the shader object. 42 | glDeleteShader(shaderObjectId) 43 | 44 | Log.w(TAG, "Compilation of shader failed.") 45 | 46 | return 0 47 | } 48 | 49 | // Return the shader object ID. 50 | return shaderObjectId 51 | } 52 | 53 | /** 54 | * Links a vertex shader and a fragment shader together into an OpenGL 55 | * program. Returns the OpenGL program object ID, or 0 if linking failed. 56 | */ 57 | private fun linkProgram(vertexShaderId: Int, fragmentShaderId: Int): Int { 58 | 59 | // Create a new program object. 60 | val programObjectId = glCreateProgram() 61 | 62 | if (programObjectId == 0) { 63 | Log.e(TAG, "Could not create new program") 64 | 65 | return 0 66 | } 67 | 68 | glAttachShader(programObjectId, vertexShaderId) 69 | 70 | glAttachShader(programObjectId, fragmentShaderId) 71 | 72 | glLinkProgram(programObjectId) 73 | 74 | val linkStatus = IntArray(1) 75 | glGetProgramiv(programObjectId, GL_LINK_STATUS, 76 | linkStatus, 0) 77 | 78 | 79 | if (linkStatus[0] == 0) { 80 | glDeleteProgram(programObjectId) 81 | 82 | Log.e(TAG, "Linking of program failed.") 83 | 84 | return 0 85 | } 86 | return programObjectId 87 | } 88 | 89 | /** 90 | * Validates an OpenGL program. Should only be called when developing the 91 | * application. 92 | */ 93 | private fun validateProgram(programObjectId: Int): Boolean { 94 | 95 | glValidateProgram(programObjectId) 96 | val validateStatus = IntArray(1) 97 | glGetProgramiv(programObjectId, GL_VALIDATE_STATUS, validateStatus, 0) 98 | 99 | return validateStatus[0] != 0 100 | } 101 | 102 | fun buildProgram(vertexShaderSource: String, fragmentShaderSource: String): Int { 103 | 104 | val program: Int 105 | 106 | // Compile the shaders. 107 | val vertexShader = compileVertexShader(vertexShaderSource) 108 | val fragmentShader = compileFragmentShader(fragmentShaderSource) 109 | 110 | // Link them into a shader program. 111 | program = linkProgram(vertexShader, fragmentShader) 112 | 113 | validateProgram(program) 114 | 115 | return program 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/opengl/ShaderProgram.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.opengl 2 | import android.content.Context 3 | import android.opengl.GLES20.glUseProgram 4 | 5 | abstract class ShaderProgram(private var vertexShaderResourceId: Int, private var fragmentShaderResourceId: Int) { 6 | 7 | private var isLoaded: Boolean = false 8 | 9 | var program: Int = 0 10 | protected set 11 | 12 | var positionAttributeLocation: Int = 0 13 | 14 | var textureCoordinatesAttributeLocation: Int = 0 15 | 16 | 17 | open fun load(context: Context) { 18 | 19 | program = ShaderHelper.buildProgram( 20 | TextResourceReader.readTextFileFromResource( 21 | context, vertexShaderResourceId), 22 | TextResourceReader.readTextFileFromResource( 23 | context, fragmentShaderResourceId)) 24 | isLoaded = true 25 | } 26 | 27 | fun useProgram() { 28 | glUseProgram(program) 29 | } 30 | 31 | companion object { 32 | const val U_MATRIX = "u_Matrix" 33 | const val U_TEXTURE_UNIT = "surface_texture" 34 | 35 | const val A_POSITION = "a_Position" 36 | const val A_TEXTURE_COORDINATES = "a_TextureCoordinates" 37 | const val A_CUTOFF_LOCATION = "cutoff" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/opengl/SpriteMesh.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.opengl 2 | 3 | import android.opengl.GLES20.GL_TRIANGLE_FAN 4 | import android.opengl.GLES20.glDrawArrays 5 | 6 | class SpriteMesh { 7 | 8 | private var vertexArray: VertexArray 9 | 10 | init { 11 | vertexArray = VertexArray(VERTEX_DATA) 12 | } 13 | 14 | fun bindData(textureProgram: ShaderProgram) { 15 | 16 | vertexArray.setVertexAttribPointer( 17 | 0, 18 | textureProgram.positionAttributeLocation, 19 | POSITION_COMPONENT_COUNT, 20 | STRIDE) 21 | 22 | vertexArray.setVertexAttribPointer( 23 | POSITION_COMPONENT_COUNT, 24 | textureProgram.textureCoordinatesAttributeLocation, 25 | TEXTURE_COORDINATES_COMPONENT_COUNT, 26 | STRIDE) 27 | } 28 | 29 | fun draw() { 30 | glDrawArrays(GL_TRIANGLE_FAN, 0, 6) 31 | } 32 | 33 | companion object { 34 | 35 | private const val BYTES_PER_FLOAT = 4 36 | private const val POSITION_COMPONENT_COUNT = 2 37 | private const val TEXTURE_COORDINATES_COMPONENT_COUNT = 2 38 | private const val STRIDE = (POSITION_COMPONENT_COUNT + TEXTURE_COORDINATES_COMPONENT_COUNT) * BYTES_PER_FLOAT 39 | 40 | 41 | val VERTEX_DATA = floatArrayOf( 42 | // Order of coordinates: X, Y, S, T 43 | // Triangle Fan 44 | 0.0f, 0.0f, 0.5f, 0.5f, 45 | -1.0f, -1.0f, 0.0f, 1.0f, 46 | 1.0f, -1.0f, 1.0f, 1.0f, 47 | 1.0f, 1.0f, 1.0f, 0.0f, 48 | -1.0f, 1.0f, 0.0f, 0.0f, 49 | -1.0f, -1.0f, 0.0f, 1.0f) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/opengl/TextResourceReader.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.opengl 2 | 3 | import android.content.Context 4 | import android.content.res.Resources 5 | 6 | import java.io.IOException 7 | 8 | object TextResourceReader { 9 | /** 10 | * Reads in text from a resource file and returns a String containing the 11 | * text. 12 | */ 13 | fun readTextFileFromResource(context: Context, resourceId: Int): String { 14 | 15 | val body = StringBuilder() 16 | 17 | try { 18 | val bufferedReader = context.resources.openRawResource(resourceId).bufferedReader() 19 | 20 | bufferedReader.use { 21 | 22 | body.append(it.readText()) 23 | body.append("\n") 24 | } 25 | } catch (e: IOException) { 26 | throw RuntimeException("Could not open resource: $resourceId", e) 27 | } catch (nfe: Resources.NotFoundException) { 28 | throw RuntimeException("Resource not found: $resourceId", nfe) 29 | } 30 | 31 | return body.toString() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/opengl/TextureShaderProgram.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.opengl 2 | 3 | import android.content.Context 4 | import android.opengl.GLES20.glGetAttribLocation 5 | import android.opengl.GLES20.glGetUniformLocation 6 | 7 | class TextureShaderProgram(vertexShaderResourceId: Int, fragmentShaderResourceId: Int) : ShaderProgram(vertexShaderResourceId, fragmentShaderResourceId) { 8 | 9 | private var uMatrixLocation: Int = 0 10 | private var uTextureUnitLocation: Int = 0 11 | private var uCutoffUnitLocation: Int = 0 12 | 13 | override fun load(context: Context) { 14 | super.load(context) 15 | 16 | uMatrixLocation = glGetUniformLocation(program, U_MATRIX) 17 | uTextureUnitLocation = glGetUniformLocation(program, U_TEXTURE_UNIT) 18 | 19 | positionAttributeLocation = glGetAttribLocation(program, A_POSITION) 20 | textureCoordinatesAttributeLocation = glGetAttribLocation(program, A_TEXTURE_COORDINATES) 21 | uCutoffUnitLocation = glGetUniformLocation(program, A_CUTOFF_LOCATION) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/opengl/VertexArray.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.opengl 2 | 3 | 4 | import android.opengl.GLES20.* 5 | import java.nio.ByteBuffer 6 | import java.nio.ByteOrder 7 | import java.nio.FloatBuffer 8 | 9 | private const val BYTES_PER_FLOAT = 4 10 | 11 | class VertexArray(vertexData: FloatArray) { 12 | 13 | private val floatBuffer: FloatBuffer = ByteBuffer 14 | .allocateDirect(vertexData.size * BYTES_PER_FLOAT) 15 | .order(ByteOrder.nativeOrder()) 16 | .asFloatBuffer() 17 | .put(vertexData) 18 | 19 | fun setVertexAttribPointer(dataOffset: Int, attributeLocation: Int, componentCount: Int, stride: Int) { 20 | 21 | floatBuffer.position(dataOffset) 22 | glVertexAttribPointer(attributeLocation, componentCount, GL_FLOAT, 23 | false, stride, floatBuffer) 24 | glEnableVertexAttribArray(attributeLocation) 25 | 26 | floatBuffer.position(0) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/opengl/ViewSurfaceTexture.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.opengl 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.SurfaceTexture 5 | import android.opengl.GLES11Ext.GL_TEXTURE_EXTERNAL_OES 6 | import android.opengl.GLES20.* 7 | import android.opengl.GLUtils 8 | import android.util.Log 9 | import android.view.Surface 10 | 11 | class ViewSurfaceTexture { 12 | 13 | private var textureWidth: Int = 0 14 | private var textureHeight: Int = 0 15 | 16 | private var surfaceTexture: SurfaceTexture? = null 17 | private var surface: Surface? = null 18 | 19 | private var surfaceTextureID = -1 20 | 21 | fun createSurface(width: Int, height: Int) { 22 | 23 | textureWidth = width 24 | textureHeight = height 25 | releaseSurface() 26 | surfaceTextureID = createTexture() 27 | if (surfaceTextureID > 0) { 28 | surfaceTexture = SurfaceTexture(surfaceTextureID) 29 | surfaceTexture!!.setDefaultBufferSize(textureWidth, textureHeight) 30 | surface = Surface(surfaceTexture) 31 | } 32 | } 33 | 34 | fun updateTexture() { 35 | surfaceTexture?.updateTexImage() 36 | } 37 | 38 | fun releaseSurface() { 39 | surface?.release() 40 | surfaceTexture?.release() 41 | surface = null 42 | surfaceTexture = null 43 | } 44 | 45 | fun beginDraw(): Canvas? { 46 | 47 | if (surface != null) { 48 | return surface?.lockHardwareCanvas() 49 | } 50 | return null 51 | } 52 | 53 | fun endDraw(surfaceCanvas: Canvas?) { 54 | if (surfaceCanvas != null) { 55 | surface?.unlockCanvasAndPost(surfaceCanvas) 56 | } 57 | } 58 | 59 | private fun createTexture(): Int { 60 | val textures = IntArray(1) 61 | 62 | // Generate the texture to where android view will be rendered 63 | glActiveTexture(GL_TEXTURE0) 64 | glGenTextures(1, textures, 0) 65 | checkGlError("Texture generate") 66 | 67 | glBindTexture(GL_TEXTURE_EXTERNAL_OES, textures[0]) 68 | checkGlError("Texture bind") 69 | 70 | glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR.toFloat()) 71 | glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR.toFloat()) 72 | glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) 73 | glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) 74 | 75 | return textures[0] 76 | } 77 | 78 | private fun checkGlError(op: String) { 79 | val error: Int = glGetError() 80 | if (error != GL_NO_ERROR) { 81 | Log.e("GL_ERROR", op + ": glError " + GLUtils.getEGLErrorString(error)) 82 | } 83 | } 84 | 85 | fun getTextureID(): Int { 86 | return surfaceTextureID 87 | } 88 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/renderers/CommonRenderer.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.renderers 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.graphics.PorterDuff 6 | import android.opengl.GLES11Ext 7 | import android.opengl.GLES20 8 | import android.opengl.GLES20.* 9 | import android.opengl.GLES30 10 | import android.opengl.Matrix 11 | import no.danielzeller.blurbehindlib.* 12 | import no.danielzeller.blurbehindlib.opengl.* 13 | 14 | 15 | class CommonRenderer(private val context: Context, internal val scale: Float, var useChildAlphaAsMask: Boolean, private val paddingVertical: Float) { 16 | 17 | var behindViewSurfaceTexture = ViewSurfaceTexture() 18 | var childViewSurfaceTexture = ViewSurfaceTexture() 19 | var isCreated = false 20 | var blurRadius = 40f 21 | 22 | private val projectionMatrixOrtho = FloatArray(16) 23 | private lateinit var spriteMesh: SpriteMesh 24 | 25 | private val fullscreenTextureShader = TextureShaderProgram(R.raw.vertex_shader, R.raw.texture_frag) 26 | private val fullscreenMaskTextureShader = TextureShaderProgram(R.raw.vertex_shader, R.raw.texture_and_mask_frag) 27 | 28 | private val gauss2PassHorizontal = TextureShaderProgram(R.raw.vertex_shader, R.raw.gauss_2_pass_horizontal) 29 | private val gauss2PassVertical = TextureShaderProgram(R.raw.vertex_shader, R.raw.gauss_2_pass_vertical) 30 | private var renderTextureHorizontal = RenderTexture() 31 | private var renderTextureVertical = RenderTexture() 32 | private var width = 0 33 | private var height = 0 34 | 35 | fun onSurfaceCreated() { 36 | glDisable(GL_DEPTH_TEST) 37 | GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f) 38 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 39 | 40 | spriteMesh = SpriteMesh() 41 | fullscreenTextureShader.load(context) 42 | fullscreenMaskTextureShader.load(context) 43 | gauss2PassHorizontal.load(context) 44 | gauss2PassVertical.load(context) 45 | } 46 | 47 | fun onSurfaceChanged(width: Int, height: Int) { 48 | this.width = width 49 | this.height = height 50 | renderTextureHorizontal.initiateFrameBuffer((width * scale).toInt(), ((height + paddingVertical) * scale).toInt()) 51 | renderTextureVertical.initiateFrameBuffer((width * scale).toInt(), ((height + paddingVertical) * scale).toInt()) 52 | behindViewSurfaceTexture.createSurface((width * scale).toInt(), ((height + paddingVertical) * scale).toInt()) 53 | childViewSurfaceTexture.createSurface(width, height) 54 | clearViewSurfaceTexture() 55 | isCreated = true 56 | } 57 | 58 | 59 | private fun clearViewSurfaceTexture() { 60 | val canvas = behindViewSurfaceTexture.beginDraw() 61 | canvas?.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) 62 | behindViewSurfaceTexture.endDraw(canvas) 63 | } 64 | 65 | private fun setupViewPort(width: Int, height: Int, offset: Float) { 66 | GLES20.glViewport(0, -(offset * 0.5f).toInt(), width, (height + offset).toInt()) 67 | val left = -1.0f 68 | val right = 1.0f 69 | val bottom = 1f 70 | val top = -1f 71 | val near = -1.0f 72 | val far = 1.0f 73 | Matrix.setIdentityM(projectionMatrixOrtho, 0) 74 | Matrix.orthoM(projectionMatrixOrtho, 0, left, right, bottom, top, near, far) 75 | } 76 | 77 | internal fun onDrawFrame() { 78 | 79 | behindViewSurfaceTexture.updateTexture() 80 | blurPass(renderTextureHorizontal, gauss2PassHorizontal, false, behindViewSurfaceTexture.getTextureID()) 81 | blurPass(renderTextureVertical, gauss2PassVertical, true, renderTextureHorizontal.fboTex) 82 | if (useChildAlphaAsMask) { 83 | renderFullscreenTexture(fullscreenMaskTextureShader) 84 | } else { 85 | renderFullscreenTexture(fullscreenTextureShader) 86 | } 87 | } 88 | 89 | 90 | private fun renderFullscreenTexture(shader: TextureShaderProgram) { 91 | setupViewPort(width, height, paddingVertical) 92 | shader.useProgram() 93 | GLES20.glUniformMatrix4fv(GLES20.glGetUniformLocation(shader.program, ShaderProgram.U_MATRIX), 1, false, projectionMatrixOrtho, 0) 94 | 95 | GLES20.glActiveTexture(GLES30.GL_TEXTURE0) 96 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, renderTextureVertical.fboTex) 97 | GLES20.glUniform1i(GLES20.glGetUniformLocation(shader.program, "mainTexture"), 0) 98 | 99 | if (useChildAlphaAsMask) { 100 | childViewSurfaceTexture.updateTexture() 101 | GLES20.glActiveTexture(GLES30.GL_TEXTURE1) 102 | GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, childViewSurfaceTexture.getTextureID()) 103 | GLES20.glUniform1i(GLES20.glGetUniformLocation(shader.program, "maskTexture"), 1) 104 | } 105 | spriteMesh.bindData(shader) 106 | spriteMesh.draw() 107 | } 108 | 109 | 110 | private fun blurPass(renderTexture: RenderTexture, blurShader: TextureShaderProgram, isVerticalPass: Boolean, bindTextureID: Int) { 111 | setupViewPort((width * scale).toInt(), ((height + paddingVertical) * scale).toInt(), 0f) 112 | renderTexture.bindRenderTexture() 113 | blurShader.useProgram() 114 | 115 | GLES20.glUniformMatrix4fv(GLES20.glGetUniformLocation(blurShader.program, ShaderProgram.U_MATRIX), 1, false, projectionMatrixOrtho, 0) 116 | 117 | GLES20.glActiveTexture(GLES30.GL_TEXTURE0) 118 | if (bindTextureID != behindViewSurfaceTexture.getTextureID()) { 119 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bindTextureID) 120 | } else { 121 | GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, bindTextureID) 122 | } 123 | GLES20.glUniform1i(GLES20.glGetUniformLocation(blurShader.program, "mainTexture"), 0) 124 | 125 | if (isVerticalPass) { 126 | GLES20.glUniform1f(GLES20.glGetUniformLocation(blurShader.program, "textureWidth"), 1f / width.toFloat() / scale) 127 | GLES20.glUniform1f(GLES20.glGetUniformLocation(blurShader.program, "textureHeight"), 0f) 128 | } else { 129 | GLES20.glUniform1f(GLES20.glGetUniformLocation(blurShader.program, "textureWidth"), 0f) 130 | GLES20.glUniform1f(GLES20.glGetUniformLocation(blurShader.program, "textureHeight"), 1f / height.toFloat() / scale) 131 | } 132 | 133 | GLES20.glUniform1f(GLES20.glGetUniformLocation(blurShader.program, "scale"), scale) 134 | GLES20.glUniform1i(GLES20.glGetUniformLocation(blurShader.program, "blurRadius"), (blurRadius * scale).toInt()) 135 | 136 | spriteMesh.bindData(blurShader) 137 | spriteMesh.draw() 138 | renderTexture.unbindRenderTexture() 139 | } 140 | 141 | fun destroyResources() { 142 | GLES20.glDeleteProgram(fullscreenTextureShader.program) 143 | GLES20.glDeleteProgram(fullscreenMaskTextureShader.program) 144 | GLES20.glDeleteProgram(gauss2PassHorizontal.program) 145 | GLES20.glDeleteProgram(gauss2PassVertical.program) 146 | 147 | behindViewSurfaceTexture.releaseSurface() 148 | renderTextureHorizontal.deleteAllTextures() 149 | renderTextureVertical.deleteAllTextures() 150 | } 151 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/renderers/GLSurfaceViewRenderer.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.renderers 2 | 3 | import android.opengl.GLES20.* 4 | import android.opengl.GLSurfaceView.Renderer 5 | import javax.microedition.khronos.egl.EGLConfig 6 | import javax.microedition.khronos.opengles.GL10 7 | 8 | class GLSurfaceViewRenderer : Renderer { 9 | 10 | lateinit var commonRenderer: CommonRenderer 11 | 12 | override fun onSurfaceCreated(glUnused: GL10, config: EGLConfig) { 13 | clearView() 14 | commonRenderer.onSurfaceCreated() 15 | } 16 | 17 | override fun onSurfaceChanged(glUnused: GL10, width: Int, height: Int) { 18 | clearView() 19 | commonRenderer.onSurfaceChanged(width, height) 20 | } 21 | 22 | override fun onDrawFrame(glUnused: GL10) { 23 | clearView() 24 | commonRenderer.onDrawFrame() 25 | } 26 | 27 | private fun clearView() { 28 | glClear(GL_COLOR_BUFFER_BIT) 29 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/java/no/danielzeller/blurbehindlib/renderers/TextureViewRenderer.kt: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib.renderers 2 | 3 | import android.content.Context 4 | import android.graphics.SurfaceTexture 5 | import android.opengl.* 6 | import android.opengl.EGL14.EGL_OPENGL_ES2_BIT 7 | import android.opengl.GLES20.glBlendFunc 8 | import android.opengl.GLES20.glEnable 9 | import android.view.TextureView 10 | import javax.microedition.khronos.egl.* 11 | import javax.microedition.khronos.egl.EGL10.* 12 | import javax.microedition.khronos.egl.EGLConfig 13 | import javax.microedition.khronos.egl.EGLContext 14 | import javax.microedition.khronos.egl.EGLDisplay 15 | import javax.microedition.khronos.egl.EGLSurface 16 | 17 | class TextureViewRenderer(val context: Context) : TextureView.SurfaceTextureListener { 18 | 19 | private lateinit var textureViewRenderThread: TextureViewRenderThread 20 | override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {} 21 | override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {} 22 | 23 | override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { 24 | textureViewRenderThread.isStopped = true 25 | return false 26 | } 27 | 28 | override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { 29 | textureViewRenderThread = TextureViewRenderThread(surface, width, height) 30 | textureViewRenderThread.start() 31 | } 32 | 33 | fun update() { 34 | textureViewRenderThread.update = true 35 | } 36 | 37 | lateinit var commonRenderer: CommonRenderer 38 | 39 | inner class TextureViewRenderThread(private val surface: SurfaceTexture, private val width: Int, private val height: Int) : Thread() { 40 | 41 | private var egl: EGL10? = null 42 | private var eglDisplay: EGLDisplay? = null 43 | private var eglContext: EGLContext? = null 44 | private var eglSurface: EGLSurface? = null 45 | var isStopped = false 46 | 47 | var update = true 48 | 49 | private fun initGL() { 50 | egl = EGLContext.getEGL() as EGL10 51 | eglDisplay = egl?.eglGetDisplay(EGL_DEFAULT_DISPLAY) 52 | egl?.eglInitialize(eglDisplay, intArrayOf(0, 0)) 53 | val eglConfig = chooseEglConfig(egl!!, eglDisplay!!) 54 | eglContext = egl!!.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE)) 55 | eglSurface = egl!!.eglCreateWindowSurface(eglDisplay, eglConfig, surface, null) 56 | 57 | } 58 | 59 | override fun run() { 60 | super.run() 61 | initGL() 62 | while (!isStopped && egl?.eglGetError() == EGL_SUCCESS) { 63 | while (!update) { 64 | Thread.sleep((1f / 90f * 1000f).toLong()) 65 | } 66 | egl!!.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext) 67 | 68 | GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f) 69 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 70 | 71 | if (!commonRenderer.isCreated) { 72 | commonRenderer.onSurfaceCreated() 73 | commonRenderer.onSurfaceChanged(width, height) 74 | glEnable(GLES20.GL_BLEND) 75 | glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA) 76 | } 77 | 78 | commonRenderer.onDrawFrame() 79 | 80 | egl!!.eglSwapBuffers(eglDisplay, eglSurface) 81 | update = false 82 | Thread.sleep((1f / 60f * 1000f).toLong()) 83 | } 84 | destroyResources() 85 | } 86 | 87 | private fun destroyResources() { 88 | surface.release() 89 | commonRenderer.behindViewSurfaceTexture.releaseSurface() 90 | egl?.eglDestroyContext(eglDisplay, eglContext) 91 | egl?.eglDestroySurface(eglDisplay, eglSurface) 92 | } 93 | 94 | private val config = intArrayOf( 95 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 96 | EGL_RED_SIZE, 8, 97 | EGL_GREEN_SIZE, 8, 98 | EGL_BLUE_SIZE, 8, 99 | EGL_ALPHA_SIZE, 8, 100 | EGL_DEPTH_SIZE, 16, 101 | EGL_STENCIL_SIZE, 0, 102 | EGL_NONE 103 | ) 104 | 105 | private fun chooseEglConfig(egl: EGL10, eglDisplay: EGLDisplay): EGLConfig { 106 | val configsCount = intArrayOf(0) 107 | val configs = arrayOfNulls(1) 108 | egl.eglChooseConfig(eglDisplay, config, configs, 1, configsCount) 109 | return configs[0]!! 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/res/raw/gauss_2_pass_horizontal.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_OES_EGL_image_external : require 2 | precision mediump float; 3 | 4 | 5 | varying vec2 v_TextureCoordinates; 6 | uniform samplerExternalOES mainTexture; 7 | uniform int blurRadius; 8 | 9 | 10 | uniform float textureWidth; 11 | uniform float textureHeight; 12 | uniform float scale; 13 | 14 | mediump float getGaussWeight(mediump float currentPos, mediump float sigma) 15 | { 16 | return 1.0 / sigma * exp(-(currentPos * currentPos) / (2.0 * sigma * sigma)); 17 | } 18 | 19 | void main() { 20 | int diameter = 2 * blurRadius + 1; 21 | vec4 sampleTex; 22 | vec4 col; 23 | float weightSum = 0.0; 24 | vec2 flippedYUV = v_TextureCoordinates; 25 | flippedYUV.y = 1.0 - flippedYUV.y; 26 | 27 | int step = int(float( blurRadius/15)*scale)+1; 28 | 29 | for(int i = 0; i < diameter; i+=step) { 30 | vec2 offset = vec2(float(i - blurRadius) * textureWidth, float(i - blurRadius) * textureHeight); 31 | sampleTex = vec4(texture2D(mainTexture, flippedYUV.st+offset)); 32 | float index = float(i); 33 | float gaussWeight = getGaussWeight(index - float(diameter - 1)/2.0, (float(diameter - 1)/2.0 + 1.0) / 2.0); 34 | col += sampleTex * gaussWeight; 35 | weightSum += gaussWeight; 36 | } 37 | 38 | gl_FragColor = vec4(col.rgb / weightSum,1.0); 39 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/res/raw/gauss_2_pass_vertical.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_OES_EGL_image_external : require 2 | precision mediump float; 3 | 4 | 5 | varying vec2 v_TextureCoordinates; 6 | uniform sampler2D mainTexture; 7 | uniform int blurRadius; 8 | 9 | 10 | uniform float textureWidth; 11 | uniform float textureHeight; 12 | uniform float scale; 13 | 14 | mediump float getGaussWeight(mediump float currentPos, mediump float sigma) 15 | { 16 | return 1.0 / sigma * exp(-(currentPos * currentPos) / (2.0 * sigma * sigma)); 17 | } 18 | 19 | void main() { 20 | int diameter = 2 * blurRadius + 1; 21 | vec4 sampleTex; 22 | vec4 col; 23 | float weightSum = 0.0; 24 | 25 | int step = int(float( blurRadius/15)*scale)+1; 26 | 27 | for(int i = 0; i < diameter; i+=step) { 28 | vec2 offset = vec2(float(i - blurRadius) * textureWidth, float(i - blurRadius) * textureHeight); 29 | sampleTex = vec4(texture2D(mainTexture, v_TextureCoordinates.st+offset)); 30 | float index = float(i); 31 | float gaussWeight = getGaussWeight(index - float(diameter - 1)/2.0, (float(diameter - 1)/2.0 + 1.0) / 2.0); 32 | col += sampleTex * gaussWeight; 33 | weightSum += gaussWeight; 34 | } 35 | 36 | gl_FragColor = col / weightSum; 37 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/res/raw/texture_and_mask_frag.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_OES_EGL_image_external : require 2 | precision mediump float; 3 | 4 | uniform sampler2D mainTexture; 5 | uniform samplerExternalOES maskTexture; 6 | varying vec2 v_TextureCoordinates; 7 | 8 | void main() 9 | { 10 | vec2 maskCoordinates = v_TextureCoordinates; 11 | maskCoordinates.y = 1.0 - maskCoordinates.y; 12 | vec4 mask = texture2D(maskTexture, maskCoordinates); 13 | if(mask.a<0.05){ 14 | discard; 15 | } 16 | vec4 color = texture2D(mainTexture, v_TextureCoordinates); 17 | gl_FragColor =vec4(mix(color.rgb,mask.rgb,mask.a).rgb,1.0); 18 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/res/raw/texture_frag.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_OES_EGL_image_external : require 2 | precision mediump float; 3 | 4 | uniform sampler2D mainTexture; 5 | varying vec2 v_TextureCoordinates; 6 | 7 | void main() 8 | { 9 | gl_FragColor = texture2D(mainTexture, v_TextureCoordinates); 10 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/res/raw/vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | uniform mat4 u_Matrix; 2 | 3 | attribute vec4 a_Position; 4 | attribute vec2 a_TextureCoordinates; 5 | 6 | varying vec2 v_TextureCoordinates; 7 | 8 | void main() 9 | { 10 | v_TextureCoordinates = a_TextureCoordinates; 11 | gl_Position = u_Matrix * a_Position; 12 | } -------------------------------------------------------------------------------- /blurbehindlib/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50dp 4 | -------------------------------------------------------------------------------- /blurbehindlib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /blurbehindlib/src/test/java/no/danielzeller/blurbehindlib/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package no.danielzeller.blurbehindlib; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.0' 5 | ext.kotlin_version = '1.3.0' 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.3.1' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | 14 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 15 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | google() 24 | jcenter() 25 | } 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielzeller/Blur-LIB-Android/20b685fa0d37285124bdb17b009024ec69751965/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 17 12:22:35 CET 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':blurbehindlib' 2 | --------------------------------------------------------------------------------