Implementazioni varie

This commit is contained in:
2018-11-08 17:25:04 +01:00
parent 0e81cc3371
commit 90dbe35bd0
81 changed files with 3284 additions and 216 deletions

1
waterfall_toolbar/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,43 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 27
defaultConfig {
minSdkVersion 21
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// google
implementation "com.android.support:cardview-v7:27.1.1"
implementation "com.android.support:design:27.1.1"
implementation 'com.android.support:appcompat-v7:27.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()
}

21
waterfall_toolbar/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,26 @@
package it.integry.plugins.waterfalltoolbar;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("it.integry.plugins.waterfall_toolbar.test", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="it.integry.plugins.waterfalltoolbar" />

View File

@@ -0,0 +1,19 @@
package it.integry.plugins.waterfalltoolbar
var density: Float? = null
data class Dp(var value: Float) {
fun toPx(): Px {
val innerDensity: Float = density ?: throw NullPointerException(
"You must set density before using DimensionUnits classes.")
return Px((value * innerDensity + 0.5f).toInt())
}
}
data class Px(var value: Int) {
fun toDp(): Dp {
val innerDensity: Float = density ?: throw NullPointerException(
"You must set density before using DimensionUnits classes.")
return Dp(value / innerDensity + 0.5f)
}
}

View File

@@ -0,0 +1,310 @@
package it.integry.plugins.waterfalltoolbar
import android.content.Context
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import android.support.annotation.RequiresApi
import android.support.v4.widget.NestedScrollView
import android.support.v7.widget.CardView
import android.support.v7.widget.RecyclerView
import android.util.AttributeSet
import android.view.View
import android.widget.ScrollView
/**
* Created by Hugo Castelani
* Date: 19/09/17
* Time: 19:30
*/
open class WaterfallToolbar : CardView {
init {
// set density to be able to use DimensionUnits
// this code must run before all the signings using DimensionUnits
if (density == null) density = resources.displayMetrics.density
}
/**
* The recycler view whose scroll is going to be listened
*/
var recyclerView: RecyclerView? = null
set(value) {
field = value
addRecyclerViewScrollListener()
}
/**
* The scroll view whose scroll is going to be listened
*/
var scrollView: ScrollView? = null
set(value) {
field = value
addScrollViewScrollListener()
}
/**
* The scroll view whose scroll is going to be listened
*/
var nestedScrollView: NestedScrollView? = null
set(value) {
field = value
addNestedScrollViewScrollListener()
}
/**
* The three variables ahead are null safe, since they are always set
* at least once in init() and a null value can't be assigned to them
* after that. So all the "!!" involving them below are fully harmless.
*/
/**
* The elevation with which the toolbar starts
*/
var initialElevation: Px? = null
set(value) {
if (value != null) {
field = value
// got to update elevation in case this value have
// been set in a running and visible activity
if (isSetup) adjustCardElevation()
} else throw NullPointerException("This field cannot be null.")
}
/**
* The elevation the toolbar gets when it reaches final scroll elevation
*/
var finalElevation: Px? = null
set(value) {
if (value != null) {
field = value
// got to update elevation in case this value have
// been set in a running and visible activity
if (isSetup) adjustCardElevation()
} else throw NullPointerException("This field cannot be null.")
}
/**
* The percentage of the screen's height that is
* going to be scrolled to reach the final elevation
*/
var scrollFinalPosition: Int? = null
set(value) {
if (value != null) {
val screenHeight = resources.displayMetrics.heightPixels
field = Math.round(screenHeight * (value / 100.0f))
// got to update elevation in case this value have
// been set in a running and visible activity
if (isSetup) adjustCardElevation()
} else throw NullPointerException("This field cannot be null.")
}
/**
* Dimension units (dp and pixel) auxiliary
*/
/**
* Values related to Waterfall Toolbar behavior in their default forms
*/
val defaultInitialElevation = Dp(0f).toPx()
val defaultFinalElevation = Dp(4f).toPx()
val defaultScrollFinalElevation = 12
/**
* Auxiliary that indicates if the view is already setup
*/
private var isSetup: Boolean = false
/**
* Position in which toolbar must be to reach expected shadow
*/
private var orthodoxPosition = Px(0)
/**
* Recycler/scroll view real position
*/
private var realPosition = Px(0)
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int?)
: super(context, attrs, defStyleAttr!!) {
init(context, attrs)
}
private fun init(context: Context?, attrs: AttributeSet?) {
// leave card corners square
radius = 0f
if (context != null && attrs != null) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaterfallToolbar)
val rawInitialElevation = typedArray.getDimensionPixelSize(
R.styleable.WaterfallToolbar_initial_elevation, defaultInitialElevation.value)
val rawFinalElevation = typedArray.getDimensionPixelSize(
R.styleable.WaterfallToolbar_final_elevation, defaultFinalElevation.value)
scrollFinalPosition = typedArray.getInteger(
R.styleable.WaterfallToolbar_scroll_final_elevation, defaultScrollFinalElevation)
this.initialElevation = Px(rawInitialElevation)
this.finalElevation = Px(rawFinalElevation)
typedArray.recycle()
} else {
initialElevation = defaultInitialElevation
finalElevation = defaultFinalElevation
scrollFinalPosition = defaultScrollFinalElevation
}
adjustCardElevation() // just to make sure card elevation is set
isSetup = true
}
private fun addRecyclerViewScrollListener() {
recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
// real position must always get updated
realPosition.value = realPosition.value + dy
mutualScrollListenerAction()
}
})
}
private fun addScrollViewScrollListener() {
scrollView?.viewTreeObserver?.addOnScrollChangedListener {
// real position must always get updated
realPosition.value = scrollView!!.scrollY
mutualScrollListenerAction()
}
}
private fun addNestedScrollViewScrollListener() {
nestedScrollView?.viewTreeObserver?.addOnScrollChangedListener {
realPosition.value = nestedScrollView!!.scrollY
mutualScrollListenerAction()
}
}
/**
* These lines are common in both scroll listeners, so they are better joined
*/
private fun mutualScrollListenerAction() {
// orthodoxPosition can't be higher than scrollFinalPosition because
// the last one holds the position in which shadow reaches ideal size
if (realPosition.value <= scrollFinalPosition!!) {
orthodoxPosition.value = realPosition.value
} else {
orthodoxPosition.value = scrollFinalPosition!!
}
adjustCardElevation()
}
/**
* Speed up the card elevation setting
*/
private fun adjustCardElevation() {
cardElevation = calculateElevation().value.toFloat()
}
/**
* Calculates the elevation based on given attributes and scroll
* @return New calculated elevation
*/
private fun calculateElevation(): Px {
// getting back to rule of three:
// finalElevation = scrollFinalPosition
// newElevation = orthodoxPosition
var newElevation: Int = finalElevation!!.value * orthodoxPosition.value / scrollFinalPosition!!
// avoid values under minimum value
if (newElevation < initialElevation!!.value) newElevation = initialElevation!!.value
return Px(newElevation)
}
/**
* Saves the view's current dynamic state in a parcelable object
* @return A parcelable with the saved data
*/
override fun onSaveInstanceState(): Parcelable? {
val savedState = SavedState(super.onSaveInstanceState())
savedState.elevation = cardElevation.toInt()
savedState.orthodoxPosition = orthodoxPosition
savedState.realPosition = realPosition
return savedState
}
/**
* Restore the view's dynamic state
* @param state The frozen state that had previously been returned by onSaveInstanceState()
*/
override fun onRestoreInstanceState(state: Parcelable) {
if (state is SavedState) {
super.onRestoreInstanceState(state.superState)
// setting card elevation doesn't work until view is created
post {
// it's safe to use "!!" here, since savedState will
// always store values properly set in onSaveInstanceState()
cardElevation = state.elevation!!.toFloat()
orthodoxPosition = state.orthodoxPosition!!
realPosition = state.realPosition!!
}
} else {
super.onRestoreInstanceState(state)
}
}
/**
* Custom parcelable to store this view's dynamic state
*/
private class SavedState : View.BaseSavedState {
var elevation: Int? = null
var orthodoxPosition: Px? = null
var realPosition: Px? = null
internal constructor(source: Parcel) : super(source)
@RequiresApi(api = Build.VERSION_CODES.N)
internal constructor(source: Parcel, loader: ClassLoader) : super(source, loader)
internal constructor(superState: Parcelable) : super(superState)
companion object {
internal val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source)
}
override fun newArray(size: Int): Array<SavedState?> {
return arrayOfNulls(size)
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="WaterfallToolbar">
<attr format="dimension" name="initial_elevation" />
<attr format="dimension" name="final_elevation" />
<attr format="integer" name="scroll_final_elevation" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Waterfall Toolbar</string>
</resources>

View File

@@ -0,0 +1,17 @@
package it.integry.plugins.waterfalltoolbar;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}